Server State vs Client State

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 2 min

Server State vs Client State

In modern React development, one of the biggest architectural shifts has been the realization that “Global State” should actually be divided into two entirely separate concepts: Client State and Server State.

Historically, developers would fetch data from an API and immediately dump it into a global Redux store alongside their UI state. This created massive, bloated state managers.

1. Client State

Client state is data that originates in the browser and is entirely controlled by the user or the application itself.

Characteristics:

  • It is ephemeral (if the user refreshes the page, the state is usually lost).
  • It is synchronous and instantly available.
  • The client is the absolute source of truth.

Examples:

  • Is the sidebar open or closed?
  • What text is currently typed into the search bar?
  • Is the user currently in “Dark Mode”?
  • The current step of a multi-page wizard form.

How to manage it:

  • useState or useReducer for local client state.
  • React Context, Zustand, Jotai, or Redux for global client state.

2. Server State

Server state is data that actually belongs to the backend database. Your React application is simply borrowing a “snapshot” of that data to display it on the screen.

Characteristics:

  • It is persistent (survives page refreshes because it lives in a database).
  • It is asynchronous (requires fetching over a network).
  • The Server is the source of truth. The snapshot in your browser can become “stale” or out-of-date at any moment if someone else modifies the database.

Examples:

  • A list of blog posts.
  • The user’s account profile details.
  • The current inventory count of a product.

The Problem with mixing them

If you put Server State into a standard Client State manager (like Redux or Zustand), you have to manually write code to handle all the complexities of asynchronous data:

  1. Tracking isLoading and isError boolean flags.
  2. Handling caching (so you don’t re-fetch data if you don’t need to).
  3. Handling cache invalidation (knowing when the snapshot is stale and needs a refresh).
  4. Deduping multiple identical requests.
  5. Implementing optimistic updates.

Writing this logic manually using useEffect and Redux Thunks is exhausting and error-prone.

The Solution: Server State Libraries

Because Server State is fundamentally different from Client State, you should use a dedicated tool built specifically for managing asynchronous cache.

The industry standards for managing Server State in React are React Query (TanStack Query), SWR, or RTK Query.

These libraries completely eliminate the need to put API data into your global state manager. You simply call a hook, and the library handles the caching, the loading states, the background refetches, and the deduping automatically.

// Example using React Query (Server State Manager)
function Posts() {
  // React Query handles the cache, loading, and error states automatically!
  const { data, isLoading, isError } = useQuery(['posts'], fetchPosts);

  if (isLoading) return <span>Loading...</span>;
  if (isError) return <span>Error fetching data</span>;

  return (
    <ul>
      {data.map(post => <li key={post.id}>{post.title}</li>)}
    </ul>
  );
}

By separating Server State from Client State, your actual global Client State (managed by Zustand or Context) shrinks by 90%, leaving only pure UI logic.

Interview Questions

Q: What is the fundamental difference between Server State and Client State?
A: Client State is ephemeral UI state strictly owned by the browser (like an open modal, dark mode preference, or form input). Server State is data persisted remotely (like a user’s database profile). Server state is inherently difficult because it requires asynchronous fetching, caching, handling latency, and synchronizing with the backend when data goes stale.