Client State vs Server State in React

Learn the difference between client state and server state in React, and which tool fits each one.

6 min read

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 stateServer state
Where it livesIn the browserIn a database or API
Who owns itThe current userA shared backend
How it changesOnly through app actionsAnywhere, at any time
Typical tooluseState or a storeTanStack 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.

App.jsxApp.jsx
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.

App.jsxApp.jsx
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

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.
RunePowered by Rune AI

Frequently Asked Questions

What is client state?

Data that lives in the browser and belongs to the current session, like an open tab, a form draft, or a theme.

What is server state?

Data that lives in a database or API and can change without the app knowing, like posts, users, or orders.

Can I put server data in useState?

You can, but you then rebuild loading, error, caching, and refetching by hand. A query library handles that lifecycle for you.

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.