React State Management

React State Management

About this quiz

This quiz about React State Management features 10 questions. It was generated by one of our users February 12, 2026.

React State Management Quiz

Welcome to the fascinating world of React state management! At its core, state is what brings your React applications to life – it's the dynamic data that changes over time and determines what users see on their screens. Whether it's a simple counter that increments with each button click, a form that captures user input, or a complex shopping cart that updates as items are added and removed, state is the beating heart of interactive web applications. Understanding how to properly manage state is crucial for building responsive, efficient, and maintainable React applications.

As your applications grow in complexity, so does the challenge of managing state effectively. React provides several built-in tools like useState and useReducer hooks for local component state, while useContext helps share state across component trees. For larger applications, you might find yourself reaching for powerful state management libraries like Redux, Zustand, or Recoil. Each approach has its own strengths and use cases – from the simplicity of local state for small components to the predictability and debugging capabilities of Redux for enterprise applications.

This quiz will test your knowledge across the entire spectrum of React state management concepts. You'll encounter questions about hook usage patterns, state lifting strategies, performance optimization techniques, and when to choose different state management solutions. Ready to prove your mastery of one of React's most fundamental concepts?

React State Management: A Comprehensive Guide

Table of Contents

  1. Introduction to State Management
  2. Local State Management
  3. Lifting State Up
  4. Context API
  5. State Management Libraries
  6. Best Practices
  7. Common Patterns
  8. Performance Considerations
  9. Testing State Management
  10. Real-World Examples

Introduction to State Management

What is State?

State in React represents data that can change over time and affects what gets rendered on the screen. Think of it as the "memory" of your component - it remembers information between renders.

Why State Management Matters
// Without state - static and boring 😴
function Counter() {
  return <div>Count: 0</div>;
}

// With state - dynamic and interactive! 🎉
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}
Types of State
Type Scope When to Use Example
Local State Single component Component-specific data Form inputs, toggles
Shared State Multiple components Data needed by siblings User authentication
Global State Entire application App-wide data Theme, language settings
Server State External data API responses User profiles, posts

Local State Management

useState Hook

The useState hook is your go-to tool for managing local component state.

Basic Usage
import React, { useState } from 'react';

function TodoItem() {
  const [isCompleted, setIsCompleted] = useState(false);
  const [text, setText] = useState('');

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Enter todo..."
      />
      <label>
        <input
          type="checkbox"
          checked={isCompleted}
          onChange={(e) => setIsCompleted(e.target.checked)}
        />
        Completed
      </label>
    </div>
  );
}
Managing Complex State
function UserProfile() {
  const [user, setUser] = useState({
    name: '',
    email: '',
    preferences: {
      theme: 'light',
      notifications: true
    }
  });

  // ❌ Don't mutate state directly
  const updateThemeWrong = (newTheme) => {
    user.preferences.theme = newTheme; // This won't trigger re-render!
    setUser(user);
  };

  // ✅ Create new objects
  const updateTheme = (newTheme) => {
    setUser(prevUser => ({
      ...prevUser,
      preferences: {
        ...prevUser.preferences,
        theme: newTheme
      }
    }));
  };

  return (
    <div>
      <input
        value={user.name}
        onChange={(e) => setUser(prev => ({ ...prev, name: e.target.value }))}
        placeholder="Name"
      />
      <select value={user.preferences.theme} onChange={(e) => updateTheme(e.target.value)}>
        <option value="light">Light</option>
        <option value="dark">Dark</option>
      </select>
    </div>
  );
}
useReducer Hook

For complex state logic, useReducer provides a more predictable way to manage state updates.

import React, { useReducer } from 'react';

// Define action types
const ACTIONS = {
  ADD_TODO: 'ADD_TODO',
  TOGGLE_TODO: 'TOGGLE_TODO',
  DELETE_TODO: 'DELETE_TODO',
  SET_FILTER: 'SET_FILTER'
};

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

function TodoApp() {
  const initialState = {
    todos: [],
    filter: 'all' // 'all', 'active', 'completed'
  };

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

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

  const toggleTodo = (id) => {
    dispatch({
      type: ACTIONS.TOGGLE_TODO,
      payload: { id }
    });
  };

  return (
    <div>
      <h1>Todo App</h1>
      {/* Component implementation */}
    </div>
  );
}
When to Use useState vs useReducer
// ✅ Use useState for:
// - Simple state (strings, numbers, booleans)
// - Independent state updates
// - Component-specific state

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

// ✅ Use useReducer for:
// - Complex state objects
// - State transitions that depend on previous state
// - Multiple related state updates

function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, initialState);
  // Multiple related operations: add item, remove item, update quantity, apply discount
}

Lifting State Up

When multiple components need to share state, you "lift" the state up to their nearest common ancestor.

Problem: Siblings Need to Share Data
// ❌ This won't work - siblings can't communicate directly
function App() {
  return (
    <div>
      <ProductList /> {/* Has products */}
      <ShoppingCart /> {/* Needs to know about selected products */}
    </div>
  );
}
Solution: Lift State Up
// ✅ Lift state to common parent
function App() {
  const [products, setProducts] = useState([]);
  const [cartItems, setCartItems] = useState([]);

  const addToCart = (product) => {
    setCartItems(prev => [...prev, product]);
  };

  const removeFromCart = (productId) => {
    setCartItems(prev => prev.filter(item => item.id !== productId));
  };

  return (
    <div>
      <ProductList 
        products={products} 
        onAddToCart={addToCart} 
      />
      <ShoppingCart 
        items={cartItems} 
        onRemoveFromCart={removeFromCart} 
      />
    </div>
  );
}

function ProductList({ products, onAddToCart }) {
  return (
    <div>
      {products.map(product => (
        <ProductCard 
          key={product.id} 
          product={product} 
          onAddToCart={onAddToCart} 
        />
      ))}
    </div>
  );
}

function ShoppingCart({ items, onRemoveFromCart }) {
  return (
    <div>
      <h2>Shopping Cart</h2>
      {items.map(item => (
        <CartItem 
          key={item.id} 
          item={item} 
          onRemove={onRemoveFromCart} 
        />
      ))}
    </div>
  );
}
The Prop Drilling Problem
// ❌ Prop drilling - passing props through multiple levels
function App() {
  const [user, setUser] = useState(null);
  
  return <Layout user={user} setUser={setUser} />;
}

function Layout({ user, setUser }) {
  return (
    <div>
      <Header user={user} setUser={setUser} />
      <Sidebar user={user} />
    </div>
  );
}

function Header({ user, setUser }) {
  return (
    <header>
      <Navigation user={user} setUser={setUser} />
    </header>
  );
}

function Navigation({ user, setUser }) {
  return (
    <nav>
      <UserMenu user={user} onLogout={() => setUser(null)} />
    </nav>
  );
}

Context API

The Context API solves prop drilling by providing a way to share data across the component tree without passing props manually.

Creating Context
import React, { createContext, useContext, useReducer } from 'react';

// 1. Create Context
const AuthContext = createContext();

// 2. Create Provider Component
export function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    isLoading: false,
    error: null
  });

  const login = async (credentials) => {
    dispatch({ type: 'LOGIN_START' });
    try {
      const user = await authService.login(credentials);
      dispatch({ type: 'LOGIN_SUCCESS', payload: user });
    } catch (error) {
      dispatch({ type: 'LOGIN_ERROR', payload: error.message });
    }
  };

  const logout = () => {
    dispatch({ type: 'LOGOUT' });
  };

  const value = {
    ...state,
    login,
    logout
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// 3. Custom Hook for Using Context
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}

// 4. Reducer
function authReducer(state, action) {
  switch (action.type) {
    case 'LOGIN_START':
      return { ...state, isLoading: true, error: null };
    case 'LOGIN_SUCCESS':
      return { ...state, isLoading: false, user: action.payload };
    case 'LOGIN_ERROR':
      return { ...state, isLoading: false, error: action.payload };
    case 'LOGOUT':
      return { ...state, user: null };
    default:
      return state;
  }
}