Problem
In useOutages (src/features/outages/hooks/useOutages.ts), the IndexedDB hydration effect and the useQuery run concurrently on mount. The hydration does:
persistedCache.get(cacheKeyStr).then((cached) => {
if (cached && cached.items.length > 0) {
const existing = queryClient.getQueryData(queryKey);
if (!existing || existing.items.length === 0) {
queryClient.setQueryData(queryKey, cached);
}
}
});
getQueryData returns undefined until the query mounts/observes, so the guard "only set if nothing exists" often evaluates undefined — the hydration writes cached data, and then the query's own first fetch resolves and overwrites it (React Query's setQueryData vs. query state: the fetch result replaces it). Consequences:
- The offline-first fast path rarely shows: on a normal (online) mount, the cached data is written and immediately replaced by the network fetch — users see the loading state anyway, so the whole hydration layer provides no visible benefit online and only helps when the fetch is slow.
- The race is order-dependent and untested: whether the user sees cached data depends on whether IndexedDB resolves before or after the fetch; there is no test pinning either outcome.
- The guard's intent (don't clobber fresher data) is defeated because the freshness comparison is against a not-yet-mounted query.
Root cause
The hydration effect was written without coordinating with the query's lifecycle (no useQuery-level initialData/placeholderData hook-in).
Why this is architecturally hard
- The correct integration is React Query's
initialData/placeholderData with a hydration promise (or queryClient.fetchQuery with the cache read inside the queryFn), which makes the cache read part of the query's data flow instead of a racing side effect — a structural change to the hook.
- The
hydratedRef one-shot behavior (companion issue) interacts: the hydration must still happen once, but the write must land in the query state, not the global cache, at the right time.
- Tests must simulate both orderings (cache resolves before/after fetch) and assert the user-visible outcome — a test surface that does not exist today.
Proposed design
Move the cache read into the query's data flow (e.g. read in queryFn first, fall back to network, or use placeholderData), eliminating the race, and add tests for both resolution orderings asserting the user sees cached data without a fetch when offline.
Acceptance criteria
Service
Tests
Out of scope
Schema versioning and empty-result caching (tracked separately).
Getting started
Good first files to read: src/features/outages/hooks/useOutages.ts.
Problem
In
useOutages(src/features/outages/hooks/useOutages.ts), the IndexedDB hydration effect and theuseQueryrun concurrently on mount. The hydration does:getQueryDatareturnsundefineduntil the query mounts/observes, so the guard "only set if nothing exists" often evaluatesundefined— the hydration writes cached data, and then the query's own first fetch resolves and overwrites it (React Query'ssetQueryDatavs. query state: the fetch result replaces it). Consequences:Root cause
The hydration effect was written without coordinating with the query's lifecycle (no
useQuery-levelinitialData/placeholderDatahook-in).Why this is architecturally hard
initialData/placeholderDatawith a hydration promise (orqueryClient.fetchQuerywith the cache read inside the queryFn), which makes the cache read part of the query's data flow instead of a racing side effect — a structural change to the hook.hydratedRefone-shot behavior (companion issue) interacts: the hydration must still happen once, but the write must land in the query state, not the global cache, at the right time.Proposed design
Move the cache read into the query's data flow (e.g. read in
queryFnfirst, fall back to network, or useplaceholderData), eliminating the race, and add tests for both resolution orderings asserting the user sees cached data without a fetch when offline.Acceptance criteria
Service
Tests
Out of scope
Schema versioning and empty-result caching (tracked separately).
Getting started
npm testGood first files to read:
src/features/outages/hooks/useOutages.ts.