Understanding React State Management: A Comprehensive Guide
João Vinezof
- •
- 08 MIN TO READ
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
- 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
- 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
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 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
Approach | Use Case | Complexity | Performance |
---|---|---|---|
useState | Local component state | Low | Best for simple state |
useReducer | Complex component state | Medium | Good for related state updates |
Context API | Global shared state | High | Best for infrequent updates |
🎯 Best Practices
- 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
- 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
- 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 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
Suggested Posts
All PostsAllAll PostsAllDiscover This Amazing GitHub Repository with Public APIs for Your Projects and Learning
João
- •
- 02 MIN TO READ