tRPC useQuery returning stale data after mutation: invalidate() vs staleTime diagnosis
0 reputation · 13 Apr 2023, 06:32 UTC
0 reputation · 13 Apr 2023, 06:32 UTC
In a tRPC client backed by TanStack Query, a component using useQuery continues to render previously cached data after a related mutation completes, even though the goal is for that query to refetch promptly. The intended behavior is immediate freshness for active queries without disabling caching for everything else.
The uncertainty is which layer is actually responsible. tRPC delegates caching entirely to TanStack Query, so freshness depends on the configured staleTime (globally or per query), while utils.invalidate() should mark matching query keys stale and trigger refetches for active observers. It is unclear whether the stale rendering is caused by a non-expired staleTime keeping the query "fresh", by invalidate() not matching the exact query key (including input parameters), or by the invalidated query simply having no active observer at the time.
Assume a current tRPC v11 setup with the TanStack Query integration; exact behavior should be verified against the installed versions, for example by watching the query state transitions and the Network tab.
Which signal best distinguishes a staleTime problem from a query-key mismatch when diagnosing this? Does invalidate() need to await the mutation's onSuccess to guarantee ordering? And is a per-procedure staleTime override preferable to invalidation for this case?
31775 reputation · 13 Apr 2023, 15:42 UTC
The clearest signal is the query’s isInvalidated flag (or the state.status shown in TanStack Query devtools). If the flag becomes true after the mutation but no network request follows, the problem is not staleTime – invalidation forces a refetch regardless of freshness. If the flag stays false, the invalidate() call did not match the query key (including its input parameters), pointing to a key‑mismatch issue.
staleTime entirely.useQuery and the key passed to utils.invalidate() differed (deep equality of inputs is required).staleTime.query.state) and run the mutation.isInvalidated become true?useQuery and the argument passed to utils.invalidate(). Compare them with a deep‑equality check (e.g., JSON.stringify or lodash.isEqual) to spot mismatches.staleTime: 0 on the useQuery call. If the symptom persists after the mutation, the cause is not staleTime.await utils.procedure.invalidate(exactInput) inside the mutation’s onSuccess. Awaiting guarantees the invalidation (and thus the refetch) finishes before any subsequent UI logic runs, preventing races with navigations or follow‑up mutations.staleTime (e.g., staleTime: 1000) as a safety net, but keep invalidation as the primary mechanism for cross‑component consistency.If you cannot tell whether the query had an active observer when the mutation ran, please confirm: was the component displaying the query mounted at the exact moment the mutation’s onSuccess executed?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.