Data Management vs State Management in React and Why Mixing Them Hurts
August 2025 · Derick Zr · 3 minutes read
I stored a user profile in Zustand.
Fetched it once on app load. Used it everywhere. Felt smart.
Then the user updated their profile in another tab. My app still showed the old data.
I was mixing data management with state management. Big mistake.
Here's the difference and why it matters.
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: User Profile (Server State)
What I did wrong:
// 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);
}, []);
Problems:
- No cache invalidation
- No automatic re-fetch
- Changes in other tabs? Invisible
What I should have done:
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)
Data from API - should be managed by TanStack Query, SWR, etc.
Client State (UI Only)
UI interactions - perfect for React useState
Current UI State
Wrong Approach: Server Data in 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. Select a user (Alice) in both sections above
- 2. Click "Simulate Server Update" to change Alice's data
- 3. Notice: Server State updates automatically, LocalStorage doesn't
- 4. This is why server data should never be in localStorage!