Back to blogs
React State Management — Part 3: Server State & Advanced Patterns

React State Management — Part 3: Server State & Advanced Patterns

July 23, 2025

Ashish Gogula Ashish Gogula

👋 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)

🔗 https://swr.vercel.app/

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.
Blog Image

Thinking about a bonus Part 4 covering persistent state, localStorage tricks, and offline-ready apps. Stay tuned!