React Query for Server State
Part of the Full Stack Developer Roadmap
In short
API data isn't like a toggle or a form field. It lives on a server, goes stale and gets shared across screens. TanStack Query handles the caching, refetching and loading states.
Using React Query as your dedicated tool for anything that ultimately comes from your backend — letting it handle caching, refetching and synchronization instead of hand-rolling it in Redux or Context.

Think of it like this
The morning newspaper
You read the paper lying on the table straight away, even if it's from this morning. When it gets old, a fresh one arrives without you asking. TanStack Query shows cached data instantly and quietly refetches in the background once it's stale.
Watch it happen
Cache first, network when needed
key ["posts"]
1/6Posts mounts and asks the cache for ["posts"].
The big ideas
What you need to know
queryKey names the data
['posts', page] identifies a query. The same key anywhere in the app means the same cached data.
useQuery reads
It gives you data, error, isPending and more, so you stop writing loading state by hand.
useMutation writes
Use it for POST, PUT and DELETE, then invalidate related queries so lists refresh.
staleTime controls freshness
By default, data counts as stale right away. Set staleTime to skip refetches for a while.
See it in code
Walk through the code
1import { useQuery } from "@tanstack/react-query";2 3type Post = { id: number; title: string };4 5export function Posts() {6 const { data, isPending, error } = useQuery({7 queryKey: ["posts"],8 queryFn: (): Promise<Post[]> =>9 fetch("https://jsonplaceholder.typicode.com/posts").then((r) => r.json()),10 staleTime: 60_000,11 });12 13 if (isPending) return <p>Loading...</p>;14 if (error) return <p>Something went wrong</p>;15 return <ul>{data.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;16}1/4useQuery is named by its key. Same key, same cached data.
Wrap the app in QueryClientProvider first. With a staleTime of 60 seconds, coming back to this screen within a minute shows the cache without a new request.
Save yourself the pain
Common mistakes
Oops
Copying query data into useState or a Redux store.
Do this instead
The cache already is your state. Read data straight from useQuery, or you'll end up showing stale copies.
Oops
Using the key ['posts'] for every page of posts.
Do this instead
Put every input in the key, like ['posts', page]. Otherwise different pages fight over one cache entry.
Your mission
Swap useEffect for useQuery
Quick check
Two components on the same screen call useQuery with queryKey ['user', 7]. How many network requests go out?
Go deeper
- 1Move a piece of server data out of Redux/Context and into a `useQuery` call instead.
- 2Compare the amount of code saved for loading/error/cache handling.
- 3Learn `invalidateQueries` for refreshing data after a mutation.
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.