ZNYThe Complete Guide to State Management in Modern React in 2026 React's state management...
React's state management landscape fragmented into specialized solutions. Here's the honest comparison.
Not all state is the same:
Server state: async, needs caching, mutation patterns — use React Query/TanStack Query
UI state: local to component, ephemeral — use useState/useReducer
Global UI state: modals, themes, sidebars — use Zustand/Jotai
Server cache: prefetched data, optimistic updates — use TanStack Query
URL state: filters, pagination — use nuqs
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
if (isLoading) return <Skeleton />;
if (error) return <Error error={error} />;
return <Profile user={data} />;
// Mutations with optimistic updates
function UpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: updateUser,
onMutate: async (newData) => {
await queryClient.cancelQueries({ queryKey: ['user'] });
const previous = queryClient.getQueryData(['user', newData.id]);
queryClient.setQueryData(['user', newData.id], newData);
return { previous };
onError: (err, newData, context) => {
queryClient.setQueryData(['user', newData.id], context.previous);
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useStore = create(
(set, get) => ({
user: null,
setUser: (user) => set({ user }),
addToCart: (item) => set((state) => ({
cart: [...state.cart, item]
total: () => get().cart.reduce((sum, item) => sum + item.price, 0),
clearCart: () => set({ cart: [] })
name: 'app-storage',
part: ['user', 'cart'] // Only persist these
// Use in components
function CartButton() {
const { cart, total } = useStore();
return <button>{cart.length} items (${total()})</button>;
import { useQueryState } from 'nuqs';
function ProductList() {
const [category, setCategory] = useQueryState('category');
const [sort, setSort] = useQueryState('sort', { defaultValue: 'price-asc' });
const [page, setPage] = useQueryState('page', { defaultValue: '1' });
// URL is now: /products?category=electronics&sort=price-asc&page=2
// Shareable, bookmarkable, back-button aware
import { atom, useAtom } from 'jotai';
// Primitive atoms
const priceAtom = atom(10);
const quantityAtom = atom(1);
// Derived atoms (computed)
const totalAtom = atom((get) => get(priceAtom) * get(quantityAtom));
// Write-only atoms
const shippingAtom = atom(
(get, set, update) => {
set(priceAtom, get.priceAtom + update);
function Cart() {
const [price] = useAtom(priceAtom);
const [quantity] = useAtom(quantityAtom);
const [total] = useAtom(totalAtom);
return <div>Total: ${total} ({quantity} items at ${price})</div>;
| Use Case | Solution |
|----------|----------|
| API data, caching | TanStack Query |
| Theme, modals | Zustand or Context |
| Forms | React Hook Form |
| URL params | nuqs |
| Animation state | useState + refs |
| Complex global state | Zustand or Redux Toolkit |
Stop using Redux for everything. TanStack Query handles server state, Zustand handles global UI state, and useState handles local state. Only reach for Redux when you have genuinely complex client-side logic that Zustand can't handle.
Build React apps faster with an all-in-one platform — includes templates, hosting, and state management patterns built-in.
This article contains affiliate links. If you sign up through the links above, I may earn a commission at no additional cost to you.
Get started with Systeme.io for free — All-in-one platform for building your online business with AI tools.