What this usually means
This typically indicates a misconfiguration in React Query’s cache policy, such as staleTime being set too high, missing or mismatched query keys, or a disconnect between query invalidation and mutation side effects. Sometimes, the refetch triggers are suppressed by specific query options or batching. In rare cases, stale data persists due to incorrect data comparison logic or side effects outside React Query’s control.
The first ten minutes — establish facts before touching code.
- 1Inspect the current query cache via DevTools—check the isStale and dataUpdatedAt values for affected queries.
- 2Verify the staleTime and cacheTime options for the relevant useQuery hooks (long staleTime delays refetch).
- 3Check that your invalidateQueries or refetchQueries calls use the exact query key (including variables).
- 4Temporarily set staleTime: 0 in your useQuery to force staleness and observe if refetches trigger.
- 5Confirm your mutation is actually firing and completing (network tab, success callbacks).
- 6Log or breakpoint inside your queryFn to watch if it’s being called when you expect a refetch.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchuseQuery and useMutation hook declarations (staleTime, queryKey, refetchOnWindowFocus)
- searchComponents that call queryClient.invalidateQueries() or refetchQueries()
- searchReact Query DevTools: look under the affected query’s cache state
- searchNetwork tab in browser DevTools: see if network requests are issued at expected times
- searchAny custom hooks wrapping useQuery to check if options are being overridden
- searchBackend endpoint logs for incoming read/write requests
Practical causes, not theory. These are the things you will actually find.
- warningstaleTime set to a high value (e.g., 5m) so data never becomes 'stale' and thus won’t refetch
- warningMismatched query keys—mutation invalidates 'user' but data is queried under ['user', id]
- warningManual cache updates (setQueryData) masking the need for a refetch
- warningrefetchOnWindowFocus set to false, so natural triggers are suppressed
- warningRace conditions: mutation completes, but UI still renders old cache before update
- warningQuery function is memoized or not updated, causing React Query to think nothing changed
Concrete fix directions. Pick the one that matches your root cause.
- buildReduce staleTime to zero (or a small value) to force data staleness after mutation
- buildAlways pass the full query key array—including variables—to invalidateQueries
- buildCall queryClient.refetchQueries() after critical mutations, not just invalidateQueries
- buildEnable refetchOnWindowFocus or refetchOnReconnect for auto-refresh behaviors
- buildAfter setQueryData, follow up with invalidateQueries if correctness is critical
- buildAudit custom hooks that mask or hardcode staleTime or query keys
A fix you cannot prove is a guess. Close the loop.
- verifiedUse React Query DevTools to confirm dataUpdatedAt and isStale update right after mutation
- verifiedCheck logs or network tab for a fresh request after an invalidation/trigger
- verifiedAdd temporary toast notifications from onSuccess of both mutation and query to see order/timing
- verifiedSwitch tabs or disconnect/reconnect network to verify auto-refetch kicks in
- verifiedManually call queryClient.refetchQueries() in console and watch for a data update
Things that make this bug worse or harder to find.
- warningAssuming invalidateQueries always causes a refetch—by default, it just marks as stale
- warningRelying on default query key inference; always supply explicit query keys
- warningBlindly using setQueryData without ensuring backend consistency
- warningNeglecting to test with multiple browser tabs (tab focus issues)
- warningSetting staleTime or cacheTime globally without considering per-query needs
- warningIgnoring custom wrappers or higher-order hooks that override React Query config
Profile Edits Not Reflected Due to Excessive staleTime
Timeline
- 13:02User updates profile; UI shows success but no visible changes.
- 13:03Engineer checks Chrome DevTools; no outgoing GET /profile after PUT.
- 13:04queryClient.invalidateQueries(['profile']) is confirmed running post-mutation.
- 13:06React Query DevTools reveals isStale: false and dataUpdatedAt unchanged after invalidation.
- 13:08Engineer discovers useQuery(['profile'], ..., { staleTime: 10 * 60 * 1000 }) in codebase.
- 13:10staleTime set to 0; UI now reflects updates instantly after mutation.
We had several user complaints about profile changes not appearing after saving, even though the backend update succeeded and the optimistic UI showed success.
Initially, we thought the mutation wasn't completing, but logs showed correct writes. After verifying that invalidateQueries was called, I checked React Query DevTools; data was stuck as fresh with no refetch triggered.
Digging into the code, I found the culprit: a staleTime of 10 minutes on the profile query, meant to ease server load, was blocking all refetches. Setting it to zero made data update instantly, solving the problem.
Root cause
staleTime on useQuery was set too high, making cache always 'fresh' and blocking refetches after mutation invalidation.
The fix
Set staleTime to 0 on the profile query, ensuring it becomes stale immediately after mutation and triggers a refetch.
The lesson
When using React Query, always tune staleTime for queries that should reflect immediate backend changes after user actions.
React Query tracks each query’s freshness with two values: staleTime and dataUpdatedAt. If data is considered 'fresh' (within staleTime), no refetch triggers even after invalidateQueries. Only when a query is marked stale will window focus, network reconnect, or manual refetches hit the network.
If your data must update right after a user-triggered mutation, defaulting to a long staleTime is a mistake. Sometimes cacheTime (how long unused queries persist) is confused with staleTime—cacheTime is less relevant here.
React Query strictly matches query key arrays. If you fetch data under ['user', id] but invalidate ['user'], the cache for ['user', id] is untouched. This is a common pitfall—especially with dynamic variables.
When in doubt, use queryClient.invalidateQueries({ predicate: key => key[0] === 'user' }) to catch all variants, but be aware this may trigger unnecessary refetches.
Calling invalidateQueries only marks data as stale. The next refetch depends on config: refetchOnWindowFocus, refetchOnReconnect, or direct refetchQueries. If your users do not focus windows or toggle network, refetch might never happen.
If immediate consistency is crucial, prefer queryClient.refetchQueries after mutation success. Use invalidateQueries for less urgent cases or when background sync suffices.
Mutations that update local state optimistically can race with queries that refetch out-of-date data. Sometimes, setQueryData is used to patch the cache, but if the actual backend lags or returns slightly different data, the cache can be poisoned.
After critical mutations, always choose between updating the cache directly or forcing a refetch—don’t try both at once unless you have a strong reason.
Frequently asked questions
Why does calling invalidateQueries not always cause an immediate refetch?
Because it only marks queries as stale. If staleTime hasn’t expired or none of refetchOnWindowFocus/refetchOnReconnect/manual refetch triggers occur, the next actual fetch may be delayed.
How do I force React Query to refetch right after a mutation?
Call queryClient.refetchQueries with the exact query key(s) in your mutation’s onSuccess handler, or set staleTime: 0 so invalidateQueries will immediately trigger a refetch.
What’s the difference between cacheTime and staleTime?
staleTime is how long cached data is considered 'fresh'; cacheTime is how long cached data sticks around after the last observer unsubscribes. Only staleTime affects refetch logic.
How do I debug mismatched query keys?
Compare your useQuery keys and invalidate or refetch calls; they must match exactly. In DevTools, look at the key path. Use a predicate function if you want to target groups of keys.