Back to blog
Article

Invalidating a query vs refetching it on a stale dashboard

Invalidating a query vs refetching it on a stale dashboard
S

StriveBit

4 min readWeb Applications

The dashboard shows 142 orders, but the database has 147

A client's ops dashboard in Next.js uses TanStack Query. The order count tile shows 142. An ops manager just added five orders through the admin panel in another tab. She switches back, sees 142, and files a bug report.

The cache is not wrong. It served what it had, and the `staleTime` was set to 60 seconds. The query had 40 seconds left. The question is what to do when the user navigates back to the dashboard or when the admin panel mutates the underlying data. You have two moves: invalidate the query, or refetch it directly. They sound similar, and in many cases the network result is identical, but the user-facing behaviour diverges.

Refetch is a direct order

`refetch` tells the query to hit the endpoint now. It does not consult `staleTime`. It does not care whether the data is fresh. It fires the request and replaces the cache when it resolves.

const { refetch } = useQuery({
  queryKey: ['orders', 'count'],
  queryFn: fetchOrderCount,
  staleTime: 60_000,
});

// on tab focus, or a manual refresh button
refetch();

This is the right call when you know the server is the only source of truth and you want the network request to happen regardless of cache state. A manual refresh button is the obvious case. Tab focus is another, though you should debounce it — a user alt-tabbing rapidly should not fire six requests.

The tradeoff: refetch always costs a round trip. On a mid-range Android phone over a flaky connection, that round trip is 800ms to 2s of a spinner. If the data was already fresh, you paid that cost for nothing.

Invalidate is a cache-level instruction

`invalidateQueries` marks the query as stale. If the query is active — meaning a component is mounted and using it — TanStack Query refetches automatically. If no component is mounted, it does nothing until the next mount or the next `refetch` call.

const queryClient = useQueryClient();

const mutation = useMutation({
  mutationFn: createOrder,
  onSuccess: () => {
    queryClient.invalidateQueries({
      queryKey: ['orders', 'count'],
    });
  },
});

This is the right call after a mutation. The admin panel created an order, so the count query is now stale. If the dashboard tab has the query active, it refetches. If it does not, the next time the user opens the dashboard, the query mounts, sees it is stale, and fetches. The user never sees 142 when the real count is 147, and you did not fire a request into a tab nobody is looking at.

The decision for this dashboard

The bug report had two fixes. First, the admin panel's `createOrder` mutation needed to invalidate `['orders', 'count']`. That handles the cross-tab case where the dashboard is mounted in another tab — TanStack Query's broadcast mechanism picks up the invalidation across tabs when you have `queryClient` persistence configured.

Second, the dashboard's tab-focus handler was calling `refetch` unconditionally. We changed it to check `isStale` first:

useEffect(() => {
  const onFocus = () => {
    if (query.isStale) refetch();
  };
  window.addEventListener('focus', onFocus);
  return () => window.removeEventListener('focus', onFocus);
}, [query.isStale, refetch]);

If the query is still within its `staleTime`, tab focus does nothing. The user sees the cached value instantly, no spinner. If the `staleTime` has elapsed, the refetch fires. This removed roughly 70% of the redundant requests we saw in the client's analytics, and the stale-number bug stopped reproducing.

Invalidation is the default after mutations. Refetch is for explicit user intent — a refresh button, a pull-to-refresh, or a focus event where stale data genuinely matters. Everything else is the cache doing its job.

Back to all articles

Ready to build something great?

We help ambitious teams build software that lasts. If you're interested in working with us or want to discuss your project, let's connect.

Get in touch