web-server-state-react-query-e77a90 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-server-state-react-query-e77a90 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: React Query owns the server cache — what was fetched, how long it stays fresh, and when it is evicted. Every entry is identified by itsqueryKey, andstaleTimeandgcTimeare two separate clocks rather than one. v5 removedonError/onSuccess/onSettledfromuseQuery, so error side effects belong in the component or on the sharedQueryCache.
Detailed Resources:
staleTime vs gcTime, consuming query options, fetch timeoutsuseMutation, invalidation, optimistic updates via the UI or the cacheuseInfiniteQuery, cursor paging, maxPagesuseSuspenseQuery, useSuspenseQueries, dependent suspense queriesoptions from it, then call useQuery(generatedOptions()). Keys and types come from one source, so a schema change surfaces as a compile error. Pattern 1.
at the parse boundary, and design the query keys deliberately, since nothing generates them. Everything from Pattern 2 onward applies unchanged.
<critical_requirements>
Read the API base URL from the environment. One build then runs against every environment.
Regenerate the client when the schema changes, and commit the generated output. The regenerated types turn a breaking API change into a compile error in review rather than a runtime error in production.
</critical_requirements>
Auto-detection: useQuery, useMutation, useInfiniteQuery, useSuspenseQuery, QueryClient, QueryClientProvider, QueryCache, MutationCache, queryKey, queryOptions, staleTime, gcTime, invalidateQueries, placeholderData, initialPageParam, openapi-ts
Applies to:
Handled elsewhere:
about state a user typed
whole responses under a key
<patterns>
Point the generator at the schema and let it emit types, service functions and query options together. The query key is generated with them, so no two call sites can disagree about it.
// openapi-ts.config.ts
export default defineConfig({
input: "./openapi.yaml",
output: "src/api-client",
plugins: ["@hey-api/typescript", "@hey-api/sdk", "@tanstack/react-query"],
});A fetch client is bundled from @hey-api/openapi-ts v0.73 — name a client plugin explicitly only to customise its options.
Full code: examples/core.md
Configure the base URL and the QueryClient defaults once, in a provider. Build the client inside useState so a re-render never swaps the cache out from under the tree.
const FIVE_MINUTES_MS = 5 * 60 * 1000;
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: { staleTime: FIVE_MINUTES_MS, refetchOnWindowFocus: false },
},
}),
);
client.setConfig({ baseUrl: process.env.API_BASE_URL ?? "" });The QueryClient half applies however the data is fetched. client is the generated client, and its setConfig() merges into the existing config rather than replacing it, so auth and a custom fetch can be set from separate call sites; a hand-written query function reads its base URL wherever it already does.
Full code: examples/core.md — provider setup, static and dynamic auth, and a fetch wrapper that aborts on a timeout
Call the generated options directly, and spread them when one call site needs a different policy.
const { data, isPending, error } = useQuery(getFeaturesOptions());
const TEN_MINUTES_MS = 10 * 60 * 1000;
const { data: slow } = useQuery({
...getFeaturesOptions(),
staleTime: TEN_MINUTES_MS,
enabled: someCondition,
});With no generator, the queryOptions helper defines the same thing by hand, and typed:
import { queryOptions } from "@tanstack/react-query";
const featuresOptions = () =>
queryOptions({ queryKey: ["features"], queryFn: getFeatures });Either way the key and the function are declared in one place, which is what stops two call sites opening two cache entries for one endpoint and an invalidateQueries reaching only one of them. Spreading the options preserves the key, so an override shares the entry rather than adding another.
Full code: examples/core.md
Component-level error covers the query a user is looking at; QueryCache and MutationCache cover everything else from one place.
new QueryClient({
queryCache: new QueryCache({
onError: (error, query) => {
// A query that already held data failed a background refetch — the component shows stale
// data and no error, so the notification is the only signal.
if (query.state.data !== undefined) notify(error.message);
},
}),
mutationCache: new MutationCache({
onError: () => notify("Operation failed."),
}),
});Full code: examples/error-handling.md
Key the query on the debounced value, and gate it with enabled so an empty term never reaches the network.
const DEBOUNCE_DELAY_MS = 500;
const debouncedTerm = useDebounce(searchTerm, DEBOUNCE_DELAY_MS);
const { data } = useQuery({
queryKey: ["search", debouncedTerm],
queryFn: () => searchApi(debouncedTerm),
enabled: debouncedTerm.length > 0,
});Keying on the raw term instead caches one entry per keystroke.
</patterns>
<red_flags>
Breaks at runtime:
onError / onSuccess / onSettled on useQuery — removed in v5, so the side effect silentlynever runs. Use the component's error or QueryCache.onError.
client.setConfig() called inside a query function — it mutates config every in-flight requestshares, so concurrent queries race for the base URL.
AbortController timeout with no clearTimeout on the success path — the timer keeps theclosure alive after the response has landed.
error branch nor an error boundary above it — a rejected fetchtakes the subtree down.
Surprising behaviour:
setConfig() merges rather than replaces, so a partial call silently keeps the previous auth.0 in v5, where v4 retried three times.fetch timeout is unrelated to staleTime and gcTime — the first bounds one request, theother two bound the cache entry.
retry left on against local mocks turns a mock that is simply missing into a slow failure.backend added is absent from the type and absent from every render that needed it.
useQuery wrapper beside a generated option gives the same endpoint two query keysand two cache entries, so invalidating one leaves the other stale.
</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.