Keep React Query lists fresh with refetchOnMount after navigation
Learn why TanStack Query serves stale list rows after edits, when invalidateQueries is not enough, and how refetchOnMount revalidates on every remount.
This article is published in English.
Sources: How `refetchOnMount` in React Query Solves Stale Data Problems Across Components
Keeping a client cache honest across routes is harder than issuing the first fetch. Picture an admin console with a users table on one screen and an edit form on another. Saving a name succeeds, navigation returns to the list, and the old name is still on screen.
That pattern shows up whenever TanStack Query (React Query) serves a warm cache instead of hitting the network again. The option refetchOnMount is a small lever that forces a freshness check every time the querying component mounts, without wiring a manual refresh button.
The Problem
Consider two routes that share one server resource:
- Users Page — Renders every user row from a list endpoint.
- Edit User Page — Persists changes for a single user.
The list route typically looks like this:
const { data } = useQuery({
queryKey: ["users"],
queryFn: getUsers,
});
The happy path feels fine until someone edits a row, saves, and returns. The mutation succeeded on the server, yet the table still shows the previous values. The question is why the UI did not pick up the write.
Why Does This Happen?
React Query stores successful responses under a queryKey. Leaving a page and coming back does not guarantee a new request. The library asks whether the key already has data; if it does, that snapshot is painted immediately.
The speed win is real, but the trade-off is staleness whenever the cache and the server diverge. Common causes include:
- Another component wrote to the same resource.
- A different route ran a mutation.
- A second browser tab changed the data.
- An external actor updated the backend.
Until something invalidates or refetches the key, the UI keeps trusting the cache.
A Real Example
A compact route tree looks like this:
Users
├── User List
└── Edit User
The list page loads users with:
const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
});
The edit page persists with:
await updateUser(userId, values);
Then it returns to the list:
navigate("/users");
After that navigation, the table can still render:
John
Alice
Michael
even though the server now stores:
Johnny
React Query simply reused the earlier list payload for ["users"].
Traditional Solutions
Teams often force a refresh by hand after a successful write:
queryClient.invalidateQueries({
queryKey: ["users"],
});
or
queryClient.refetchQueries({
queryKey: ["users"],
});
Invalidation and explicit refetch are still the right tools right after a mutation you own. They fall short when freshness must recover after navigation, background work, or updates that happened outside the current component tree. In those cases you want the query to verify the server when the page mounts again.
Enter refetchOnMount
The option is literally named:
refetchOnMount
It instructs the library to fetch again whenever the component that owns the query mounts. The list query becomes:
const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
refetchOnMount: true,
});
The sequence is then:
- Open the Users page.
- Cache the response.
- Edit a record elsewhere.
- Navigate back to Users.
- Mount the list component again.
- React Query requests a fresh payload automatically.
No manual refresh control, no full page reload, and no ad-hoc refetch buried in navigation handlers.
Before vs After
Without refetchOnMount
Open Users
↓
Data Cached
↓
Edit User
↓
Back to Users
↓
Old Cached Data
With refetchOnMount
Open Users
↓
Data Cached
↓
Edit User
↓
Back to Users
↓
Component Mounts
↓
Automatic Refetch
↓
Fresh Updated Data
Complete Example
Start with the hook import:
import { useQuery } from "@tanstack/react-query";
A list component that always revalidates on mount:
const Users = () => {
const { data, isLoading } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
refetchOnMount: true,
}); if (isLoading) {
return <p>Loading...</p>;
} return (
<div>
{data?.map((user) => (
<p key={user.id}>{user.name}</p>
))}
</div>
);
};
Each revisit of that page asks the server for the latest rows instead of trusting the cache alone.
Should You Always Use It?
No. When every mutation carefully invalidates the related keys, mount-time refetching can be redundant.
It still helps when:
- Several screens mutate the same resource.
- Users bounce between routes often.
- Data changes outside the mounted component.
- The UI must stay close to server truth.
- Occasional extra network calls are acceptable.
For resources that change frequently, enabling the flag is a low-cost way to shrink stale displays.
When It Shines
The pattern fits multi-screen back-office tools such as:
- Consoles that manage accounts and roles
- Catalog editors for merchandise
- Stock and warehouse screens
- Internal operator consoles
- Fulfillment and purchase workflows
- Relationship-management workspaces
- Staff self-service hubs
Those products usually edit one dataset from many routes, so automatic refetch on mount keeps list views aligned with recent writes.
Final Thoughts
Caching is a core strength of React Query, and the same cache is what lets outdated rows linger after navigation. Setting refetchOnMount tells the library to re-check the server whenever the page mounts again.
Treat it as a safety net, not a substitute for invalidation. After a mutation you control, invalidate or update the affected queries first. Use mount refetch for the cases where another screen, tab, or process may have changed the data without your local mutation handler noticing.