
React State Management — Part 3: Server State & Advanced Patterns
July 23, 2025
Ashish Gogula

July 23, 2025
Ashish Gogula
👋 New here? This is Part 3 of my React State Management series.
👉 Part 1 - React’s Built-In Tools (useState, useReducer Context)
(opens in new tab)👉 Part 2 - Client-Side Libraries (Redux, Zustand, Recoil, Jotai) (opens in new tab)
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.
Unlike client-side state (like a selected tab or a dark mode toggle), server state:
Examples include:
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:
You’d have to build all of this yourself. Thankfully, you don’t have to.
🔗 https://tanstack.com/query/latest (opens in new tab)
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())
});🔗 https://swr.vercel.app/ (opens in new tab)
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);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.
This separation helps you:
“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.
Use useMemo, useCallback, or libraries like reselect (for Redux) to prevent unnecessary recalculations or re-renders.
Persist only what matters (e.g., auth token or theme preference) using localStorage/sessionStorage or libraries like redux-persist.
Don’t just show a spinner. Show useful messages and retry options. Use skeleton loaders or placeholders.
