Stop 11 · State ManagementTopics · 5/5

Caching & Synchronization

Part of the Full Stack Developer Roadmap

In short

You edit your name, hit save, and the header still shows the old one. That's a stale cache. The fix is knowing what to refetch after each change, and when old data is good enough.

Keeping client-cached server data in sync with what's actually in the database — stale data after a mutation is one of the most common full-stack UX bugs.

The blob character collects fresh photocopies from a machine while an old crumpled stack sits in a bin.

Think of it like this

Photocopied class notes

Before exams, everyone photocopies the topper's notes. Copies are quick to read, but when the topper fixes a mistake, every copy is wrong. The class rep announces 'fresh copies ready' and people swap. Your cache is the photocopy, a mutation is the correction, and invalidation is that announcement.

Watch it happen

Cache, write, invalidate, refetch

OrderList
Query cache
API

useQuery(['orders'])

1/7The list asks the cache for the 'orders' key.

The big ideas

What you need to know

Query keys are addresses

['orders', { status: 'pending' }] is one cache slot. The same key means shared data and a single request.

staleTime decides freshness

Within staleTime, cached data is used without refetching. After it, data still shows but refetches in the background.

Invalidate after writes

When a mutation succeeds, invalidate the keys it affects so those lists and details refetch.

Refetch on focus and reconnect

TanStack Query refetches stale data when the tab regains focus or the network comes back. Keep that on for most apps.

See it in code

Walk through the code

tsx
1const queryClient = useQueryClient();2 3const orders = useQuery({4  queryKey: ["orders"],5  queryFn: () => api<Order[]>("/orders"),6  staleTime: 60_000, // fresh for one minute7});8 9const placeOrder = useMutation({10  mutationFn: (input: NewOrder) =>11    api<Order>("/orders", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(input) }),12  onSuccess: () => queryClient.invalidateQueries({ queryKey: ["orders"] }),13});14// placeOrder.mutate({ item: "masala dosa", qty: 2 })

1/3The list is cached under ['orders'] and treated as fresh for one minute.

TanStack Query v5 syntax. invalidateQueries matches by prefix, so ['orders', 42] is refreshed too.

Save yourself the pain

Common mistakes

Oops

Pushing the new item into a local useState copy after a mutation and forgetting other screens.

Do this instead

Invalidate the query key instead. Every component using that key refetches and stays consistent.

Oops

Setting staleTime to Infinity for data other users can change.

Do this instead

Pick a staleTime that matches how fast the data changes, and invalidate after your own writes.

Your mission

Watch the cache work

~30 min

Quick check

After a successful 'add comment' mutation, the comments list still shows old data. What's the usual fix?

Go deeper

  1. 1After a mutation, e.g. creating a post, invalidate or update the relevant cached query.
  2. 2Learn React Query's `staleTime` and `refetchOnWindowFocus` and how they affect freshness.
  3. 3Test a scenario where two browser tabs edit the same data and see how your app handles it.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge