All Articles
Tutorial8 min read

React State Management in 2024: What to Use When

Confused about React state management? Understand when to use useState, useContext, Zustand, or server state libraries.

T

TechGyanic

November 26, 2025

React State Management in 2024: What to Use When

React state management has become simpler, not more complex. The ecosystem matured, and now we have clear answers for most situations.

The State Categories

1. Local Component State

Data used by one component only.

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Use: useState, useReducer

2. Shared UI State

Data shared between a few components (theme, modal state).

// ThemeContext.js
const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Usage
function Header() {
  const { theme, setTheme } = useContext(ThemeContext);
  return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
    Toggle Theme
  </button>;
}

Use: useContext + useState

3. Global Client State

Complex state shared across many components.

// store.js using Zustand
import { create } from 'zustand';

const useStore = create((set) => ({
  cart: [],
  addToCart: (item) => set((state) => ({ 
    cart: [...state.cart, item] 
  })),
  removeFromCart: (id) => set((state) => ({ 
    cart: state.cart.filter(item => item.id !== id) 
  })),
  clearCart: () => set({ cart: [] })
}));

// Usage
function CartButton() {
  const { cart, addToCart } = useStore();
  return <span>Cart ({cart.length})</span>;
}

Use: Zustand (simple), Jotai (atomic), Redux Toolkit (complex)

4. Server State

Data from your backend that needs caching, sync, and updates.

// queries.js using TanStack Query
function useProducts(category) {
  return useQuery({
    queryKey: ['products', category],
    queryFn: () => fetch(`/api/products?category=${category}`).then(r => r.json()),
    staleTime: 5 * 60 * 1000, // Cache for 5 minutes
  });
}

// Usage
function ProductList({ category }) {
  const { data, isLoading, error } = useProducts(category);
  
  if (isLoading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  
  return data.map(product => <ProductCard key={product.id} {...product} />);
}

Use: TanStack Query (React Query), SWR

Decision Framework

Is it from the server?
  → Yes: TanStack Query or SWR
  → No: Continue

Used by single component?
  → Yes: useState or useReducer
  → No: Continue

Used by 2-3 nearby components?
  → Yes: Lift state up + props
  → No: Continue

Is it simple global state?
  → Yes: Zustand or Jotai
  → No: Redux Toolkit (complex state machines)

Common Anti-Patterns

1. Redux for Everything

Don't use Redux for:

  • Form state (use react-hook-form)
  • Server data (use React Query)
  • Simple shared state (use Zustand)

2. Context for Large State

Context re-renders all consumers on any change:

// Bad: Everything re-renders when any user field changes
<UserContext.Provider value={{ user, setUser, preferences, setPreferences }}>

Split contexts or use Zustand instead.

3. Over-fetching with useEffect

// Bad
useEffect(() => {
  fetch('/api/data').then(setData);
}, []);

// Good: Use React Query
const { data } = useQuery({
  queryKey: ['data'],
  queryFn: fetchData
});

My 2024 Stack

  • Local: useState, useReducer
  • Forms: react-hook-form
  • Server data: TanStack Query
  • Global UI: Zustand
  • Theme/Auth: Context (updates rarely)

Simple is maintainable. Choose the smallest tool that solves your problem.

reactstate-managementzustandreduxfrontend
Share this article
T

Written by

TechGyanic

Sharing insights on technology, software architecture, and development best practices.