Draft. This piece originally ran on LinkedIn. Paste the full article here in
/keystatic, then turn Draft off. The structure below is scaffolding to delete.
The pitch for TanStack Query is usually "it fetches data for you". That undersells it. What it actually does is make you admit that server state is not client state, and once you accept that, most of the boilerplate you've been writing disappears.
Client state and server state are different problems
Client state is yours: it lives in the browser, you own its lifecycle, and it's correct the moment you set it. Server state is a cache of something that lives elsewhere — it can be stale, it can be refetched by someone else, and it can be wrong the instant you render it.
Most state management pain comes from putting both in the same store.
// Redux-era: you own loading, error, staleness, and refetching by hand.
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");
const [data, setData] = useState<Project[]>([]);
const [error, setError] = useState<Error | null>(null);You end up writing the same three useState calls in every component that touches the network.
The mental model
A query is a subscription to a cache entry keyed by a serializable value. That's the whole idea.
const { data, isPending, error } = useQuery({
queryKey: ["projects", { company }],
queryFn: () => fetchProjects(company),
});Because the key is just data, two components asking for the same key share one cache entry, one in-flight request, and one source of truth.
What the key actually buys you
- Deduplication — ten components mounting at once produce one request
- Background refresh — stale data renders immediately, fresh data swaps in
- Garbage collection — unused entries are dropped on a timer
Where it stops being convenient
It isn't a global store, and reaching for it as one is the most common way to get burned.
If you find yourself putting something in the query cache that never came from a server, it probably belongs in
useStateor a real store.
What I'd tell someone starting today
Model the key first, and the rest tends to fall into place. Write the key as though it were a cache entry you'd want to invalidate precisely, because that's exactly what it is.