Learn React state management with useState hook and master event handling patterns. Build interactive components that respond to user input and update dynamically.
State is what makes React components dynamic and interactive. While props allow data to flow from parent to child, state allows components to manage their own data that can change over time.
Think of state as the component's memory - it remembers information between renders and triggers re-renders when it changes. Every interactive feature you see on modern websites, from form inputs to shopping carts to social media likes, relies on state management.
In this lesson, you'll master the useState hook, learn how to handle user events, and build truly interactive React components that respond to user actions in real-time.
State represents data that can change over time and belongs to a specific component. When state changes, React automatically re-renders the component to reflect the new state.
What is State?
State is a JavaScript object that stores property values that belong to a component. When the state object changes, the component re-renders.
Before Hooks (Class Components):
1class Counter extends React.Component {
2 constructor(props) {
3 super(props);
4 this.state = { count: 0 };
5 }
6
7 increment = () => {
8 this.setState({ count: this.state.count + 1 });
9 }
10
11 render() {
12 return (
13 <div>
14 <p>Count: {this.state.count}</p>
15 <button onClick={this.increment}>+</button>
16 </div>
17 );
18 }
19}With Hooks (Modern React):
1function Counter() {
2 const [count, setCount] = useState(0);
3
4 const increment = () => {
5 setCount(count + 1);
6 };
7
8 return (
9 <div>
10 <p>Count: {count}</p>
11 <button onClick={increment}>+</button>
12 </div>
13 );
14}Key State Concepts:
The useState hook is the most commonly used React hook. It allows functional components to have state.
Basic useState Syntax:
1const [stateVariable, setStateFunction] = useState(initialValue);Simple Examples:
1// String state
2const [name, setName] = useState('');
3
4// Number state
5const [count, setCount] = useState(0);
6
7// Boolean state
8const [isVisible, setIsVisible] = useState(false);
9
10// Array state
11const [todos, setTodos] = useState([]);
12
13// Object state
14const [user, setUser] = useState({ name: '', email: '' });State Update Patterns:
1function StateExamples() {
2 const [count, setCount] = useState(0);
3 const [user, setUser] = useState({ name: 'John', age: 25 });
4 const [items, setItems] = useState(['apple', 'banana']);
5
6 // Simple update
7 const increment = () => setCount(count + 1);
8
9 // Functional update (recommended for complex logic)
10 const incrementSafe = () => setCount(prevCount => prevCount + 1);
11
12 // Object update (spread syntax)
13 const updateUserAge = () => {
14 setUser(prevUser => ({ ...prevUser, age: prevUser.age + 1 }));
15 };
16
17 // Array update
18 const addItem = () => {
19 setItems(prevItems => [...prevItems, 'new item']);
20 };
21
22 return (
23 <div>
24 <p>Count: {count}</p>
25 <p>User: {user.name}, Age: {user.age}</p>
26 <p>Items: {items.join(', ')}</p>
27 </div>
28 );
29}Common useState Patterns:
setIsOpen(!isOpen) or setIsOpen(prev => !prev)setCount(count + 1) or setCount(prev => prev + 1)setItems([...items, newItem])setUser({...user, name: 'New Name'})React uses SyntheticEvents, which wrap native DOM events to provide consistent behavior across browsers.
Basic Event Handling:
1function EventExamples() {
2 const [message, setMessage] = useState('');
3
4 const handleClick = () => {
5 setMessage('Button clicked!');
6 };
7
8 const handleMouseEnter = () => {
9 setMessage('Mouse entered!');
10 };
11
12 const handleChange = (event) => {
13 setMessage(event.target.value);
14 };
15
16 return (
17 <div>
18 <button onClick={handleClick}>Click me</button>
19 <div onMouseEnter={handleMouseEnter}>Hover over me</div>
20 <input onChange={handleChange} placeholder="Type something" />
21 <p>Message: {message}</p>
22 </div>
23 );
24}Event Handler Patterns:
1function InteractiveComponent() {
2 const [formData, setFormData] = useState({
3 username: '',
4 email: '',
5 password: ''
6 });
7
8 // Handle input changes
9 const handleInputChange = (event) => {
10 const { name, value } = event.target;
11 setFormData(prevData => ({
12 ...prevData,
13 [name]: value
14 }));
15 };
16
17 // Handle form submission
18 const handleSubmit = (event) => {
19 event.preventDefault(); // Prevent default form submission
20 console.log('Form submitted:', formData);
21 };
22
23 // Handle key press
24 const handleKeyPress = (event) => {
25 if (event.key === 'Enter') {
26 console.log('Enter pressed!');
27 }
28 };
29
30 return (
31 <form onSubmit={handleSubmit}>
32 <input
33 name="username"
34 value={formData.username}
35 onChange={handleInputChange}
36 onKeyPress={handleKeyPress}
37 placeholder="Username"
38 />
39 <input
40 name="email"
41 type="email"
42 value={formData.email}
43 onChange={handleInputChange}
44 placeholder="Email"
45 />
46 <button type="submit">Submit</button>
47 </form>
48 );
49}Common Event Types:
Learn advanced patterns for managing complex state scenarios.
Multiple State Variables:
1function ProfileEditor() {
2 const [name, setName] = useState('');
3 const [email, setEmail] = useState('');
4 const [bio, setBio] = useState('');
5 const [isEditing, setIsEditing] = useState(false);
6 const [errors, setErrors] = useState({});
7
8 const validateForm = () => {
9 const newErrors = {};
10 if (!name.trim()) newErrors.name = 'Name is required';
11 if (!email.trim()) newErrors.email = 'Email is required';
12 setErrors(newErrors);
13 return Object.keys(newErrors).length === 0;
14 };
15
16 const handleSave = () => {
17 if (validateForm()) {
18 setIsEditing(false);
19 // Save logic here
20 }
21 };
22
23 return (
24 <div>
25 {isEditing ? (
26 <EditMode
27 name={name}
28 setName={setName}
29 email={email}
30 setEmail={setEmail}
31 bio={bio}
32 setBio={setBio}
33 errors={errors}
34 onSave={handleSave}
35 onCancel={() => setIsEditing(false)}
36 />
37 ) : (
38 <ViewMode
39 name={name}
40 email={email}
41 bio={bio}
42 onEdit={() => setIsEditing(true)}
43 />
44 )}
45 </div>
46 );
47}State with Arrays and Objects:
1function TodoApp() {
2 const [todos, setTodos] = useState([]);
3 const [inputValue, setInputValue] = useState('');
4
5 const addTodo = () => {
6 if (inputValue.trim()) {
7 const newTodo = {
8 id: Date.now(),
9 text: inputValue.trim(),
10 completed: false
11 };
12 setTodos(prevTodos => [...prevTodos, newTodo]);
13 setInputValue('');
14 }
15 };
16
17 const toggleTodo = (id) => {
18 setTodos(prevTodos =>
19 prevTodos.map(todo =>
20 todo.id === id ? { ...todo, completed: !todo.completed } : todo
21 )
22 );
23 };
24
25 const removeTodo = (id) => {
26 setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
27 };
28
29 return (
30 <div>
31 <input
32 value={inputValue}
33 onChange={(e) => setInputValue(e.target.value)}
34 onKeyPress={(e) => e.key === 'Enter' && addTodo()}
35 placeholder="Add a todo"
36 />
37 <button onClick={addTodo}>Add</button>
38
39 {todos.map(todo => (
40 <div key={todo.id}>
41 <span
42 style={{
43 textDecoration: todo.completed ? 'line-through' : 'none'
44 }}
45 onClick={() => toggleTodo(todo.id)}
46 >
47 {todo.text}
48 </span>
49 <button onClick={() => removeTodo(todo.id)}>Remove</button>
50 </div>
51 ))}
52 </div>
53 );
54}State Update Batching:
React automatically batches multiple state updates that happen in the same event handler for performance:
1function BatchingExample() {
2 const [count, setCount] = useState(0);
3 const [flag, setFlag] = useState(false);
4
5 const handleClick = () => {
6 // These will be batched together - only one re-render
7 setCount(count + 1);
8 setFlag(!flag);
9 // Component re-renders once with both updates
10 };
11
12 return (
13 <div>
14 <p>Count: {count}</p>
15 <p>Flag: {flag.toString()}</p>
16 <button onClick={handleClick}>Update Both</button>
17 </div>
18 );
19}Build a complete shopping cart with state management, event handling, and dynamic updates.
1function ShoppingCart() {
2 // State for cart items
3 const [cartItems, setCartItems] = useState([]);
4
5 // State for product form
6 const [productForm, setProductForm] = useState({
7 name: '',
8 price: '',
9 quantity: 1
10 });
11
12 // State for UI feedback
13 const [message, setMessage] = useState('');
14 const [isLoading, setIsLoading] = useState(false);
15
16 // Handle input changes
17 const handleInputChange = (event) => {
18 const { name, value } = event.target;
19 setProductForm(prev => ({
20 ...prev,
21 [name]: value
22 }));
23 };
24
25 // Add item to cart
26 const addToCart = () => {
27 if (!productForm.name || !productForm.price) {
28 setMessage('Please fill in all fields');
29 return;
30 }
31
32 setIsLoading(true);
33
34 // Simulate API call
35 setTimeout(() => {
36 const newItem = {
37 id: Date.now(),
38 name: productForm.name,
39 price: parseFloat(productForm.price),
40 quantity: parseInt(productForm.quantity),
41 total: parseFloat(productForm.price) * parseInt(productForm.quantity)
42 };
43
44 setCartItems(prev => [...prev, newItem]);
45 setProductForm({ name: '', price: '', quantity: 1 });
46 setMessage(`Added "${newItem.name}" to cart!`);
47 setIsLoading(false);
48
49 // Clear message after 3 seconds
50 setTimeout(() => setMessage(''), 3000);
51 }, 500);
52 };
53
54 // Update item quantity
55 const updateQuantity = (id, newQuantity) => {
56 if (newQuantity < 1) return;
57
58 setCartItems(prev =>
59 prev.map(item =>
60 item.id === id
61 ? { ...item, quantity: newQuantity, total: item.price * newQuantity }
62 : item
63 )
64 );
65 };
66
67 // Remove item from cart
68 const removeItem = (id) => {
69 setCartItems(prev => prev.filter(item => item.id !== id));
70 setMessage('Item removed from cart');
71 setTimeout(() => setMessage(''), 2000);
72 };
73
74 // Calculate total
75 const cartTotal = cartItems.reduce((total, item) => total + item.total, 0);
76
77 // Clear entire cart
78 const clearCart = () => {
79 if (cartItems.length > 0) {
80 setCartItems([]);
81 setMessage('Cart cleared!');
82 setTimeout(() => setMessage(''), 2000);
83 }
84 };
85
86 return (
87 <div className="shopping-cart">
88 <h1>🛒 Interactive Shopping Cart</h1>
89
90 {/* Add Product Form */}
91 <div className="add-product-form">
92 <h2>Add Product</h2>
93 <div className="form-row">
94 <input
95 type="text"
96 name="name"
97 value={productForm.name}
98 onChange={handleInputChange}
99 placeholder="Product name"
100 className="form-input"
101 />
102 <input
103 type="number"
104 name="price"
105 value={productForm.price}
106 onChange={handleInputChange}
107 placeholder="Price"
108 step="0.01"
109 min="0"
110 className="form-input"
111 />
112 <input
113 type="number"
114 name="quantity"
115 value={productForm.quantity}
116 onChange={handleInputChange}
117 min="1"
118 className="form-input quantity-input"
119 />
120 </div>
121 <button
122 onClick={addToCart}
123 disabled={isLoading}
124 className="add-button"
125 >
126 {isLoading ? '⏳ Adding...' : '✅ Add to Cart'}
127 </button>
128 </div>
129
130 {/* Status Message */}
131 {message && (
132 <div className="message">
133 {message}
134 </div>
135 )}
136
137 {/* Cart Items */}
138 <div className="cart-section">
139 <div className="cart-header">
140 <h2>Cart Items ({cartItems.length})</h2>
141 {cartItems.length > 0 && (
142 <button onClick={clearCart} className="clear-button">
143 🗑️ Clear Cart
144 </button>
145 )}
146 </div>
147
148 {cartItems.length === 0 ? (
149 <p className="empty-cart">Your cart is empty</p>
150 ) : (
151 <div className="cart-items">
152 {cartItems.map(item => (
153 <div key={item.id} className="cart-item">
154 <div className="item-info">
155 <h3>{item.name}</h3>
156 <p>${item.price.toFixed(2)} each</p>
157 </div>
158 <div className="quantity-controls">
159 <button
160 onClick={() => updateQuantity(item.id, item.quantity - 1)}
161 className="qty-button"
162 >
163 −
164 </button>
165 <span className="quantity">{item.quantity}</span>
166 <button
167 onClick={() => updateQuantity(item.id, item.quantity + 1)}
168 className="qty-button"
169 >
170 +
171 </button>
172 </div>
173 <div className="item-total">
174 ${item.total.toFixed(2)}
175 </div>
176 <button
177 onClick={() => removeItem(item.id)}
178 className="remove-button"
179 >
180 ❌
181 </button>
182 </div>
183 ))}
184 </div>
185 )}
186
187 {cartItems.length > 0 && (
188 <div className="cart-total">
189 <h2>Total: ${cartTotal.toFixed(2)}</h2>
190 <button className="checkout-button">
191 🛍️ Proceed to Checkout
192 </button>
193 </div>
194 )}
195 </div>
196 </div>
197 );
198}This shopping cart demonstrates advanced state and event handling concepts:
Multiple State Variables:
cartItems - Array of cart itemsproductForm - Object for form inputsmessage - String for user feedbackisLoading - Boolean for loading stateEvent Handling Patterns:
State Update Patterns:
Advanced Features:
Key Learning Points:
You've mastered state and event handling! Next, you'll learn about conditional rendering and working with lists:
You'll build more complex interfaces that adapt to user interactions and data changes.
Your progress is saved in this browser, on this device only.