frontend-data-fetching-cache-contracts

Installation
SKILL.md

Frontend data fetching cache contracts

Client data libraries cache reads by key and serve them stale-while-revalidate; the read side breaks when a write does not invalidate the exact key the reader subscribes to, when the key itself is unstable or mis-partitioned, or when fetch timing (waterfalls, focus refetch, pagination growth, fetchPolicy) is left to a default that does not match the data. This lens owns reads and caching; the mutation and rollback that produce the new data are the sibling skill.

Checklist (lead with the trap)

  1. Invalidate the key the reader actually uses — or the read never refreshes. After a write, a list/detail stays stale unless the exact query key / cache tag it subscribes to is invalidated. TanStack matches fuzzily by key prefix (['todos'] invalidates ['todos', {page}]) unless exact: true; a mismatched key leaves stale data, an over-broad one refetches the world. RTK Query needs providesTags on the query and invalidatesTags on the mutation to line up (plus a LIST id so a newly created row appears). Apollo keys the normalized cache by __typename:id, so a new list item will not show up unless you update the list field or refetch. Whether the mutation fires the invalidation is the sibling skill; whether it targets the reader's key is here.
  2. Build the key from stable, serialized params — not a fresh object each render. Keys hash deterministically and object property order does not matter, but the key must be JSON-serializable and stable across renders. A non-serializable value (Date, class, function) or one that changes identity every render fragments the cache into permanent misses; omitting a param the fetch actually depends on collides two different results into one entry. Array item order in the key is significant.
  3. Partition the cache key by what selects a different result set — not by what re-slices it. Filter/sort/search params that change which rows come back belong in the key (TanStack key array, Apollo keyArgs, RTK serializeQueryArgs); params that only re-view the same cached data do not. Wrong grouping means one filter's results bleed into another, or the cache never hits.
  4. Parallelize independent requests; prefetch dependent ones — do not await in series. Sequential awaits, or a child query that needs the parent's result, render as a waterfall. Flatten by hoisting/restructuring the API, running independent queries together (useQueries/parallel), or prefetching in the parent or router so the second request starts before the child mounts.
  5. Set staleTime/gcTime and focus/reconnect refetch on purpose — defaults differ per library. TanStack defaults staleTime: 0 (refetch on every mount/focus) and gcTime 5 min; SWR revalidates on focus and reconnect by default and dedupes within a short window; RTK Query does not refetch on focus/reconnect unless you opt in (setupListeners + refetchOnFocus/refetchOnReconnect) and drops unused data after ~60s. Over-fetching is a refetch storm on a rarely-changing resource; under-fetching is staleTime: Infinity with no invalidation, so it never refreshes. Do not carry one library's mental default into another.
  6. Infinite/paginated cache: append vs replace, and cap growth. useSWRInfinite/useInfiniteQuery accumulate pages; a merge or setter that overwrites drops earlier pages. Unbounded page arrays balloon memory and slow back-navigation — TanStack maxPages (with both getNextPageParam and getPreviousPageParam) caps retained pages; on refetch it refetches pages sequentially from the first to avoid stale-cursor duplicates, and if the query is garbage-collected pagination restarts at page one (lost scroll position). Apollo needs a field-policy merge (with keyArgs) or the list overwrites; RTK Query infinite scroll uses serializeQueryArgs + merge + forceRefetch (or build.infiniteQuery).
  7. Apollo fetchPolicy/nextFetchPolicy: do not strand a query off-network or in a refetch loop. cache-only/cache-first can leave a screen showing empty or stale data that never revalidates; cache-and-network/network-only without a nextFetchPolicy that demotes to cache-first refetches on every render. nextFetchPolicy resets to the initial policy when variables change (reason variables-changed).
  8. Fetch the shape the view needs — not more, not one-at-a-time. Selecting whole objects when the screen renders two fields is over-fetch; N+1 per-row detail requests a single list/useQueries batch could cover is under-fetch and usually also a waterfall.

Quick probes

Treat hits as leads; confirm the write->read refresh seam and the key at the call site. Route mechanical key hygiene (unstable keys, missing deps, infinite-query property order) to the authoritative linter @tanstack/eslint-plugin-query (rules exhaustive-deps, no-unstable-deps, infinite-query-property-order) — it catches key mistakes faster than review.

Installs
1
GitHub Stars
1
First Seen
3 days ago
frontend-data-fetching-cache-contracts — voidmatcha/frontend-niche-skills