Deck 17 - Data Fetching + Caching Patterns (React Query/SWR style) (objective)
Goal: review async data code like a senior: caching, invalidation, query keys, optimistic updates, retries, pagination, and edge cases.
You learn: what/why (under the hood), common bugs, and evaluator-grade review wording.
Output style: PASS/PARTIAL/FAIL + 2-3 issues + fix approach.
Mental model: server state vs client state (again, in caching terms).
Server state is remote and can be stale; it needs caching, invalidation, and refetch.
Client state is local and authoritative (UI toggles, selections).
Mental model: cache key is the identity of data.
Query keys define what data is cached.
If the key is wrong/unstable, you get collisions, refetch storms, or stale data.
Mental model: stale vs fresh vs invalidated.
Fresh: within staleTime. Stale: eligible to refetch. Invalidated: marked stale now.
Stale does not mean wrong; it means revalidation may run.
Mental model: deduping and request coalescing.
Libraries dedupe in-flight requests for same key.
Without it, components can trigger duplicate network calls.
Mental model: retries are not always safe.
Retrying POST/PUT can duplicate writes unless idempotent.
Prefer retries for GET; for mutations, ensure idempotency or disable retries.
Mental model: optimistic update is a temporary local illusion.
You update cache immediately, then confirm or roll back.
Requires rollback logic and careful invalidation on settle.
Mental model: ‘keep previous data’ vs flicker.
Keeping previous data avoids UI flicker during refetch.
But can show stale content; use loading indicators or ‘updating’ state.
Mental model: background refetch triggers.
Focus/reconnect/refetchInterval can refetch unexpectedly.
Reviewer: ensure it is intentional and not causing rate limits.
Mental model: pagination and cache shape.
Paginated data can be cached per page or as an infinite list.
Wrong key/merge strategy can duplicate items or break ordering.
Mental model: normalization vs caching per query.
React Query caches per query response.
Normalization can help large graphs but increases complexity; use when needed.
Mental model: cancellation and race protection.
Requests should be cancellable (AbortController) or at least guarded against stale responses.
Libraries handle some races; manual fetch needs explicit guard.
Mental model: error states are part of UX contract.
Handle loading/error/empty states consistently.
Do not treat errors as just console logs.
Mental model: validation at the boundary.
Treat API responses as unknown; validate (Zod) at boundary.
Prevents runtime crashes from unexpected shapes.
Mental model: cache invalidation is a correctness problem.
If you forget invalidation, UI shows stale data.
If you over-invalidate, you cause refetch storms.
Mental model: queryFn purity.
Query functions should be deterministic for a given key.
They should not depend on unstable closures or mutable globals.
Mental model: suspense vs manual loading.
Suspense centralizes loading handling.
But requires boundaries and can complicate error handling; match approach to app needs.
Mental model: auth tokens and caching.
Cache may contain user-specific data.
On logout/login, clear caches or scope keys by user to avoid leaking data across sessions.
Mental model: prefetching improves perceived performance.
Prefetch likely next data (hover, route transition).
But avoid wasteful prefetch that spikes bandwidth.
Mental model: selecting and transforming data.
Use ‘select’ to derive view models in query layer.
Keeps UI components simpler and avoids repeated transforms.
Pattern: stable query keys (rule).
Keys should be arrays with stable primitives.
Example: [‘user’, userId] not [‘user’, { id: userId }] unless object is stable/serialized.
Pattern: include all params in key.
Search/sort/page/filter params must be part of key.
Otherwise cached results can be returned for wrong inputs.
Pattern: use enabled for conditional queries.
If a query depends on an ID, set enabled: !!id.
Prevents fetching with undefined and reduces error noise.
Pattern: staleTime vs cacheTime (concept).
staleTime controls refetch eligibility; cacheTime controls how long unused data stays in memory.
Tune to UX and data volatility.