Data Management vs State Management in React and Why Mixing Them Hurts

August 2025 · Derick Zr

One of the most common mistakes I see in React apps is storing server data in a client global state.

For example: putting the user profile inside React Context, Zustand, or Redux.

At first, it feels fine — you fetch the user once, save it in a global store, and use it anywhere in your app. But over time, this creates problems:

  • The data can become stale unless you manually re-fetch it.
  • You have to handle loading, error states, and caching yourself.
  • You end up re-inventing what libraries like TanStack Query, SWR, or RTK Query already do — but less efficiently.

Data Management (Server State)

Server state is data that:

  • Lives in your backend (not the browser)

  • Can change without your app knowing

  • Needs features like caching, re-fetching, pagination, and background updates

Examples:

  • User profile

  • Product catalog

  • Analytics reports

For this, I use TanStack Query.
It acts like a smart global store for server data:

  • Fetch once, reuse anywhere in the app

  • Auto re-fetch when the data might be stale

  • Cache between navigations

State Management (Client State)

Client state is data that:

  • Exists only in the browser

  • Is often tied to UI interactions

  • Does not need to be synced with a server

Examples:

  • Modal open/close state

  • Sidebar toggle

  • Active tab in a component

  • Form input values

For this, plain React state (useState) or lightweight libraries are enough.

Example 1: User Profile — Server State

Wrong approach (mixing responsibilities)

// profileStore.js (Zustand)
import create from 'zustand';
 
export const useProfileStore = create((set) => ({
  profile: null,
  setProfile: (profile) => set({ profile }),
}));
 
// App.jsx
const { setProfile } = useProfileStore();
useEffect(() => {
  fetch('/api/me')
    .then((res) => res.json())
    .then(setProfile);
}, []);
 

The problem here:

  • No cache invalidation

  • No automatic re-fetch

  • If another tab updates the profile, you’ll never know

Better approach with TanStack Query

import { useQuery } from '@tanstack/react-query';
 
function useProfile() {
  return useQuery({
    queryKey: ['profile'],
    queryFn: () => fetch('/api/me').then((res) => res.json())
  });
}
 
// Anywhere in your app:
const { data: profile, isLoading } = useProfile();
 

Benefits:

  • Cache across the app
  • Auto re-fetch when needed
  • Handles loading & error states out of the box

Example 2: Sidebar Toggle — Client State

Here, we don’t need TanStack Query — it’s purely UI state.

function Sidebar() {
  const [isOpen, setIsOpen] = useState(false);
 
  return (
    <>
      <button onClick={() => setIsOpen(!isOpen)}>
        Toggle Sidebar
      </button>
      {isOpen && <div className="sidebar">I’m open!</div>}
    </>
  );
}
 

This state:

  • Doesn’t come from a server
  • Doesn’t need caching or refetching
  • Lives entirely in the browser

Key Takeaways

  • Server State (Data Management)
    Use TanStack Query, SWR, or similar tools to manage data that comes from a backend.

  • Client State (State Management)
    Use React state or a small store for UI interactions and local-only state.

Separating the two keeps your code simpler, more predictable, and easier to maintain — especially as your app grows.

Interactive Example

See the difference in action with this interactive demo:

Server State (The Right Way)

React State

Data from API - should be managed by TanStack Query, SWR, etc.

Client State (UI Only)

React State

UI interactions - perfect for React useState

Current UI State

Sidebar: Closed
Active Tab: profile
Selected User ID: None

Wrong Approach: Server Data in LocalStorage

LocalStorage

This creates stale data problems - avoid this pattern!

⚠️ Why This Is Wrong

  • • Data becomes stale (not synced with server)
  • • No automatic refetching
  • • Manual cache invalidation needed
  • • Duplicates server state management logic

Test the Difference:

  1. 1. Select a user (Alice) in both sections above
  2. 2. Click "Simulate Server Update" to change Alice's data
  3. 3. Notice: Server State updates automatically, LocalStorage doesn't
  4. 4. This is why server data should never be in localStorage!
Data Management vs State Management in React and Why Mixing Them Hurts