Introduction to State Management

What is state management and why is it important in React applications

What is State Management?

State management is the practice of managing the data that controls how your application behaves and what it displays. In React, "state" refers to any data that can change over time and affects what's rendered on the screen.

As applications grow, managing state becomes increasingly complex. Data needs to be shared between components, synchronized with servers, and kept consistent across the UI. This is where state management solutions come in.

Types of State

  • Local State — Component-specific data (form inputs, toggles)
  • Global State — Data shared across many components (user auth, theme)
  • Server State — Data from external APIs (fetched data, cache)
  • URL State — Data in the URL (search params, routes)
  • Form State — Form data, validation, submission status

Local State with useState

import { useState } from 'react';

function Counter() {
  // Local state - only this component needs it
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  
  return (
    <form>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Name"
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
        placeholder="Email"
      />
    </form>
  );
}

Complex State with useReducer

import { useReducer } from 'react';

// Reducer function handles state transitions
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: Date.now(), text: action.text, done: false }];
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'DELETE_TODO':
      return state.filter(todo => todo.id !== action.id);
    default:
      return state;
  }
}

function TodoList() {
  const [todos, dispatch] = useReducer(todoReducer, []);
  
  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', text });
  };
  
  const toggleTodo = (id) => {
    dispatch({ type: 'TOGGLE_TODO', id });
  };
  
  return (
    <div>
      <button onClick={() => addTodo('New task')}>Add Todo</button>
      {todos.map(todo => (
        <div key={todo.id} onClick={() => toggleTodo(todo.id)}>
          {todo.done ? '✓' : '○'} {todo.text}
        </div>
      ))}
    </div>
  );
}

The Problem: Prop Drilling

// Without state management, data must be passed through every level
function App() {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  return (
    <Layout user={user} theme={theme}>
      <Header user={user} theme={theme} />
      <Main user={user} theme={theme}>
        <Sidebar user={user} theme={theme} />
        <Content user={user} theme={theme}>
          <UserProfile user={user} />  {/* Finally uses user! */}
        </Content>
      </Main>
    </Layout>
  );
}

// This is called "prop drilling" - passing props through
// many layers of components that don't use them

When Do You Need State Management?

Scenario Solution
Simple form inputs useState
Complex component state useReducer
Share state between siblings Lift state up
Theme, auth, language Context API
Large app, frequent updates Redux, Zustand, Jotai
Server data with caching TanStack Query, SWR

State Management Landscape

🏠 Built-in React

  • • useState - local component state
  • • useReducer - complex state logic
  • • Context API - share state globally

📦 External Libraries

  • • Redux / Redux Toolkit - predictable state
  • • Zustand - minimal and fast
  • • Jotai - atomic state
  • • MobX - reactive state

🌐 Server State

  • • TanStack Query (React Query)
  • • SWR by Vercel
  • • RTK Query (Redux Toolkit)

📝 Form State

  • • React Hook Form
  • • Formik
  • • Zod for validation

💡 Key Principles

  • • Start simple - useState is often enough
  • • Keep state close to where it's used
  • • Lift state up only when needed
  • • Separate client state from server state
  • • Choose tools based on your app's needs, not hype