DEV Community

ZNY
ZNY

Posted on

The Complete Guide to State Management in Modern React in 2026

The Complete Guide to State Management in Modern React in 2026

React's state management landscape fragmented into specialized solutions. Here's the honest comparison.

The State Management Spectrum

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

TanStack Query — Server State Done Right

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'] });
    }
  });
}
Enter fullscreen mode Exit fullscreen mode

Zustand — Simple Global State

import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

const useStore = create(
  devtools(
    persist(
      (set, get) => ({
        user: null,
        cart: [],

        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>;
}
Enter fullscreen mode Exit fullscreen mode

URL State for Filters and Search

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
}
Enter fullscreen mode Exit fullscreen mode

Jotai — Atomic State Management

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(
  null,
  (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>;
}
Enter fullscreen mode Exit fullscreen mode

When to Use What

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

Conclusion

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.

Top comments (0)