
React State Management — Part 3: Server State & Advanced Patterns
Published: 7/23/2025
👋 New here? This is Part 3 of my React State Management series.
👉 Part 1 - React’s Built-In Tools (useState, useReducer Context)
👉 Part 2 - Client-Side Libraries (Redux, Zustand, Recoil, Jotai)
In the first two parts of this series, we looked at React’s built-in tools and powerful client-side libraries like Redux Toolkit, Zustand, and Recoil.
But modern apps don’t just manage UI state. They also deal with server state — data that comes from APIs, changes over time, and needs to be synced reliably.
That’s where tools like React Query and SWR shine.
What Is Server State?
Unlike client-side state (like a selected tab or a dark mode toggle), server state:
- Lives outside the browser — on a backend or remote database
- Is fetched via API calls (e.g., REST, GraphQL)
- Can become stale quickly
- Is shared between users or devices
- Needs strategies for caching, refetching, error handling, and loading states
Examples include:
- User profile data from an API
- A list of products in an e-commerce app
- Comments on a blog post
The Problem with Managing Server State Yourself
Trying to handle server state with just useState and useEffect can get ugly fast
useEffect(() => {
fetch('/api/posts')
.then((res) => res.json())
.then(setPosts)
.catch(setError);
}, []);
What about:
- Caching?
- Refetching in the background?
- Avoiding duplicate requests?
- Showing stale data while refetching?
- Synchronizing after a mutation?
You’d have to build all of this yourself. Thankfully, you don’t have to.
React Query (aka TanStack Query)
🔗 https://tanstack.com/query/latest
React Query is a game-changer when it comes to working with remote data. It turns your API interactions into declarative hooks.
import { useQuery } from '@tanstack/react-query';
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(res => res.json())
});
Key Features:
- Built-in caching and background refetching
- Automatic retries and error handling
- Stale-while-revalidate
- Pagination, infinite scroll
- Devtools for debugging
- Works with any data source (REST, GraphQL, Firebase, etc.)
When to Use:
- Any time you fetch data from an API
- Apps where real-time freshness matters (dashboards, feeds, etc.)
- When you want to simplify your data logic a lot
SWR (by Vercel)
SWR stands for stale-while-revalidate and is a lightweight, elegant data fetching library from Vercel.
import useSWR from 'swr';
const fetcher = url => fetch(url).then(res => res.json());
const { data, error, isLoading } = useSWR('/api/user', fetcher);
Why Devs Love It:
- Simple and intuitive
- Automatically revalidates stale data
- Lightweight bundle (~4kb)
- Great for quick projects and simple use cases
When to Use:
- You want a minimal setup
- You’re building on Next.js or Vercel platforms
- You’re okay with fewer features than React Query
Combining Tools: The Hybrid Approach
Here’s the sweet spot:
Use React Query or SWR for server state, and tools like Zustand or Redux for client-side or UI state.
Example Pattern:
- Use useQuery to fetch products
- Store modal open/close state in Zustand
- Use Redux for app-wide filters and sort options
This separation helps you:
- Keep server data fresh
- Keep client interactions snappy
- Avoid mixing concerns
Advanced Patterns & Best Practices
1. State Colocation
“Keep state as close to where it’s used as possible.”
If a state is only relevant to a component, don’t lift it up to global state unnecessarily.
2. Derived State Optimization
Use useMemo, useCallback, or libraries like reselect (for Redux) to prevent unnecessary recalculations or re-renders.
3. Selective Persistence
Persist only what matters (e.g., auth token or theme preference) using localStorage/sessionStorage or libraries like redux-persist.
4. Error & Loading Management
Don’t just show a spinner. Show useful messages and retry options. Use skeleton loaders or placeholders.
TL;DR
- Server state is external data (fetched, shared, updated often).
- Don’t manage it manually — use React Query or SWR to simplify caching, errors, and loading.
- Combine with client-side state tools like Zustand, Jotai, or Redux for the best results.
- Use colocated and derived state wisely for better performance and maintainability.
