João Vinezof
react

Jan 02, 2025

Understanding React State Management: A Comprehensive Guide

Understanding React State Management: A Comprehensive Guide
— scroll down — read more

Understanding React State Management: A Comprehensive Guide

🔄 Understanding React State Management: A Comprehensive Guide

📖 8 min read | 💻 Basic | ⚡ With Code Examples

🎯 Introduction

State management is fundamental to React applications. Whether you're building a simple form or a complex application, understanding how to effectively manage state is crucial for creating responsive and maintainable applications.

📚 Basic State Management with useState

The useState hook is the foundation of state management in React functional components.

1import { useState } from "react";
2
3function Counter() {
4  const [count, setCount] = useState(0);
5
6  return (
7    <div>
8      <p>Count: {count}</p>
9      <button onClick={() => setCount(count + 1)}>Increment</button>
10    </div>
11  );
12}
13

🔑 Key Concepts

  1. State Updates are Asynchronous
1// ❌ Incorrect: May lead to race conditions
2setCount(count + 1);
3setCount(count + 1);
4
5// ✅ Correct: Using functional updates
6setCount((prevCount) => prevCount + 1);
7setCount((prevCount) => prevCount + 1);
8
  1. State is Immutable
1function TodoList() {
2  const [todos, setTodos] = useState([]);
3
4  // ❌ Incorrect: Mutating state directly
5  const addTodo = (todo) => {
6    todos.push(todo); // Never modify state directly
7  };
8
9  // ✅ Correct: Creating new state
10  const addTodo = (todo) => {
11    setTodos([...todos, todo]);
12  };
13}
14

🔄 Complex State Management with useReducer

When your component's state logic becomes complex or when you have multiple state updates that depend on each other, useReducer provides a more structured and predictable approach.

1import { useReducer } from "react";
2
3const initialState = { count: 0 };
4
5function reducer(state, action) {
6  switch (action.type) {
7    case "increment":
8      return { count: state.count + 1 };
9    case "decrement":
10      return { count: state.count - 1 };
11    default:
12      throw new Error();
13  }
14}
15
16function Counter() {
17  const [state, dispatch] = useReducer(reducer, initialState);
18
19  return (
20    <div>
21      Count: {state.count}
22      <button onClick={() => dispatch({ type: "increment" })}>+</button>
23      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
24    </div>
25  );
26}
27
Diagram showing useReducer flow

useReducer Flow Diagram

When to use useReducer?

  • Complex state logic involving multiple sub-values
  • State updates that depend on other state values
  • Need for more predictable state updates
  • Large components with many state updates

🌍 Global State with Context API

Context API is ideal when you need to share state between multiple components without prop drilling.

1import { createContext, useContext, useState } from "react";
2
3// Create context
4const ThemeContext = createContext();
5
6// Provider component
7function ThemeProvider({ children }) {
8  const [theme, setTheme] = useState("light");
9
10  return (
11    <ThemeContext.Provider value={{ theme, setTheme }}>
12      {children}
13    </ThemeContext.Provider>
14  );
15}
16
17// Consumer component
18function ThemedButton() {
19  const { theme, setTheme } = useContext(ThemeContext);
20
21  return (
22    <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
23      Current theme: {theme}
24    </button>
25  );
26}
27
Context API flow diagram

Context API Data Flow

When to use Context API?

  • Sharing global state (theme, user authentication, language)
  • Avoiding prop drilling through multiple component levels
  • Managing application-wide settings or preferences
  • Need for state that many components need to access and modify
ApproachUse CaseComplexityPerformance
useStateLocal component stateLowBest for simple state
useReducerComplex component state MediumGood for related state updates
Context APIGlobal shared stateHighBest for infrequent updates

🎯 Best Practices

  1. State Colocation
1// ❌ Bad: Global state for local concern
2const GlobalContext = createContext();
3
4// ✅ Good: Local state
5function Form() {
6  const [formData, setFormData] = useState({});
7  // ...
8}
9
  1. State Splitting
1// ❌ Bad: One large state object
2const [state, setState] = useState({
3  user: null,
4  posts: [],
5  comments: [],
6  preferences: {},
7});
8
9// ✅ Good: Split state
10const [user, setUser] = useState(null);
11const [posts, setPosts] = useState([]);
12const [comments, setComments] = useState([]);
13
  1. Derived State
1// ❌ Bad: Storing derived state
2const [items, setItems] = useState([]);
3const [itemCount, setItemCount] = useState(0);
4
5// ✅ Good: Derive values
6const [items, setItems] = useState([]);
7const itemCount = items.length;
8

🚀 Real-World Example

Let's build a simple shopping cart component that demonstrates these concepts:

1// cart-context.jsx
2import { createContext, useState, useContext } from "react";
3
4const CartContext = createContext();
5
6function CartProvider({ children }) {
7  const [items, setItems] = useState([]);
8
9  const addItem = (product) => {
10    setItems((prevItems) => {
11      const existingItem = prevItems.find((item) => item.id === product.id);
12
13      if (existingItem) {
14        return prevItems.map((item) =>
15          item.id === product.id
16            ? { ...item, quantity: item.quantity + 1 }
17            : item
18        );
19      }
20
21      return [...prevItems, { ...product, quantity: 1 }];
22    });
23  };
24
25  const removeItem = (productId) => {
26    setItems((prevItems) => prevItems.filter((item) => item.id !== productId));
27  };
28
29  // Derived values
30  const totalItems = items.reduce((sum, item) => sum + item.quantity, 0);
31  const totalPrice = items.reduce(
32    (sum, item) => sum + item.price * item.quantity,
33    0
34  );
35
36  return (
37    <CartContext.Provider
38      value={{
39        items,
40        addItem,
41        removeItem,
42        totalItems,
43        totalPrice,
44      }}
45    >
46      {children}
47    </CartContext.Provider>
48  );
49}
50
51// App.tsx
52import { useCart, CartProvider } from "./cart-context";
53
54function Product({ id, name, price }) {
55  const { addItem } = useCart();
56
57  return (
58    <div className="border p-4 rounded">
59      <h3>{name}</h3>
60      <p>${price}</p>
61      <button onClick={() => addItem({ id, name, price })}>Add to Cart</button>
62    </div>
63  );
64}
65
66function CartSummary() {
67  const { items, totalItems, totalPrice, removeItem } = useCart();
68
69  return (
70    <div className="border p-4 mt-4">
71      <h2>Cart ({totalItems} items)</h2>
72      {items.map((item) => (
73        <div key={item.id} className="flex justify-between items-center">
74          <span>
75            {item.name} x {item.quantity}
76          </span>
77          <button onClick={() => removeItem(item.id)}>Remove</button>
78        </div>
79      ))}
80      <div className="mt-4 font-bold">Total: ${totalPrice}</div>
81    </div>
82  );
83}
84
85export default function App() {
86  const products = [
87    { id: 1, name: "T-Shirt", price: 29.99 },
88    { id: 2, name: "Jeans", price: 59.99 },
89  ];
90
91  return (
92    <CartProvider>
93      <div className="m-auto container mt-10">
94        <h1 className="mb-2 text-lg">Shopping Store</h1>
95        <div className="grid grid-cols-2 gap-4">
96          {products.map((product) => (
97            <Product key={product.id} {...product} />
98          ))}
99        </div>
100        <CartSummary />
101      </div>
102    </CartProvider>
103  );
104}
105
Shopping cart state management demo

Shopping Cart in Action


📝 Conclusion

Effective state management is crucial for React applications. By understanding these patterns and best practices, you can build more maintainable and performant applications.

🔗 Resources


Share this post