Client state and server state are two different kinds of data, and confusing them is the most common source of stale UI. Client state lives in the browser and belongs to the current session.
Server state lives in a database or API and can change without the app knowing. The whole question is about lifecycle: who changes the data and how often it goes stale.
The difference at a glance
| Client state | Server state | |
|---|---|---|
| Where it lives | In the browser | In a database or API |
| Who owns it | The current user | A shared backend |
| How it changes | Only through app actions | Anywhere, at any time |
| Typical tool | useState or a store | TanStack Query |
The table shows why one tool cannot do both jobs well. Client state changes only when the user acts. Server state goes stale on its own, so it needs caching, refetching, and invalidation.
What belongs in client state
Client state covers things like the open tab, a form draft, or a theme. It is local, synchronous, and nobody else can change it.
function Tabs() {
const [activeTab, setActiveTab] = useState("summary");
return (
<div>
<button onClick={() => setActiveTab("summary")}>Summary</button>
<button onClick={() => setActiveTab("reviews")}>Reviews</button>
</div>
);
}The active tab is only relevant to this session. useState is the right tool because nothing outside the browser will ever change it. The rule is simple: if a reload can reasonably discard the value, it is probably client state.
What belongs in server state
Server state covers posts, users, orders, and anything fetched from an API. It arrives asynchronously and can be out of date a second later.
import { useQuery } from "@tanstack/react-query";
function PostList() {
const { isPending, error, data } = useQuery({
queryKey: ["posts"],
queryFn: () => fetch("/api/posts").then((res) => res.json()),
});
if (isPending) return <p>Loading...</p>;
if (error) return <p>Could not load posts.</p>;
return <ul>{data.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}TanStack Query handles loading, error, caching, and refetching. Writing the same logic with useState and an Effect means rebuilding all of that by hand, which is where bugs creep in. The queryKey tells the cache which data this is, so two components asking for the same key share one request.
Why mixing them fails
Storing server data in a global store creates duplicate sources of truth. The store holds a copy, the server holds the real value, and nothing keeps the two in sync.
A shopping cart kept in a global store and on the server is the classic case. Two tabs, one logout, or a background sync each leave one copy stale, and the app shows a total that no longer matches the server.
The result is stale lists, double requests, and loading logic scattered across components. Keeping server data in a query library leaves client stores small and simple. A query library solves this by making the cache the single source of truth and revalidating it against the server.
The rule of thumb
- Client state: useState, useReducer, context, or a store like Zustand or Redux Toolkit.
- Server state: TanStack Query, SWR, or a framework loader.
If a value can change behind your back, treat it as server state. If only the user can change it, client state is fine.
This rule is not about file location: a value fetched from the server but copied into a local store is still server state. When in doubt, ask who else can change the value.
One common confusion
Some people call a fetched value client state once it lands in a component. The data still came from the server, so its lifecycle is server state. The key question is not where the data sits right now, but who can change it next.
The split is covered in the state management decision guide, and the fetching details are in how to fetch API data in React. A working query setup is in how to set up TanStack Query in React.
Rune AI
Key Insights
- Client state lives in the browser and changes only through app actions.
- Server state lives in a backend and can change behind your back.
- Use useState or a store for client state.
- Use TanStack Query or SWR for server state.
Frequently Asked Questions
What is client state?
What is server state?
Can I put server data in useState?
Conclusion
Client state and server state have different lifecycles, so they need different tools. Keep client state in React state or a store, and keep server cache in a query library.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.