State Management in React

Explore React state management patterns from local state to context API and external libraries like Redux and Zustand.

State management is how you handle data that changes over time in your React application. Choosing the right approach depends on your app's complexity and requirements.

Local Component State

Use useState for data that only affects a single component. This is the simplest and most common approach.

Lifting State Up

When multiple components need the same data, lift the state to their closest common ancestor and pass it down via props.

Context API

React's built-in solution for sharing state across many components without prop drilling. Best for low-frequency updates like themes or user data.

External Libraries

For complex applications, consider Redux, Zustand, or Jotai. These provide predictable state containers with developer tools and middleware support.

Server State

Tools like React Query and SWR specialize in managing server state, handling caching, refetching, and synchronization.

Code Examples

Context API Pattern

ThemeContext.jsx
import { createContext, useContext, useState } from 'react';

// Create context
const ThemeContext = createContext(null);

// Provider component
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('dark');

  const toggleTheme = () => {
    setTheme(prev => prev === 'dark' ? 'light' : 'dark');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Custom hook for consuming context
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Usage in component
function Header() {
  const { theme, toggleTheme } = useTheme();
  return (
    <header className={theme}>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </header>
  );
}

Reducer Pattern

TodoReducer.jsx
import { useReducer } from 'react';

const initialState = { todos: [], filter: 'all' };

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.payload,
          completed: false
        }]
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case 'SET_FILTER':
      return { ...state, filter: action.payload };
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', payload: text });
  };

  return (
    <div>
      {/* TodoList and controls */}
    </div>
  );
}

Zustand Store

store.js
import { create } from 'zustand';

const useStore = create((set) => ({
  // State
  count: 0,
  user: null,

  // Actions
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  setUser: (user) => set({ user }),
  logout: () => set({ user: null }),
}));

// Usage in components
function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);

  return (
    <button onClick={increment}>
      Count: {count}
    </button>
  );
}

function UserProfile() {
  const user = useStore((state) => state.user);
  const logout = useStore((state) => state.logout);

  if (!user) return null;
  return (
    <div>
      <p>Welcome, {user.name}</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Frequently Asked Questions

When should I use Context vs Redux/Zustand?

Use Context for low-frequency updates like themes or auth state. For high-frequency updates or complex state logic with many actions, external libraries provide better performance and developer experience.

Does using Context cause performance issues?

Context can cause unnecessary re-renders if not used carefully. Split contexts by update frequency, memoize values and components, and consider libraries like Zustand for frequently-updated state.

What's the difference between server state and client state?

Server state is data from your backend (fetched, cached, synchronized). Client state is local UI state (forms, modals, selections). Tools like React Query handle server state; Context/Redux handle client state.

Need React Help?

Slashdev.io builds production-ready React applications for businesses of all sizes.

Get in Touch