React State
Overview
State is data in React components that can change over time. State enables components to respond to user interactions, network requests, or other events. This chapter will learn how to use the useState Hook to manage state in function components.
🔄 What is State
State vs Props
jsx
// Props are passed from outside, read-only
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// State is internal to the component, mutable
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}State Characteristics
- Locality: Each component instance has its own state
- Asynchronous Updates: State updates may be asynchronous
- Triggers Re-render: State changes cause components to re-render
🎣 useState Hook
Basic Usage
jsx
import { useState } from 'react';
function SimpleCounter() {
// [state value, update function] = useState(initial value)
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(0);
return (
<div>
<h2>Counter: {count}</h2>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>Reset</button>
</div>
);
}Different Types of State
jsx
function StateDemoApp() {
// Number state
const [age, setAge] = useState(25);
// String state
const [name, setName] = useState('');
// Boolean state
const [isVisible, setIsVisible] = useState(true);
// Array state
const [items, setItems] = useState(['Apple', 'Banana']);
// Object state
const [user, setUser] = useState({
name: 'John',
email: 'john@example.com'
});
const addItem = () => {
setItems([...items, `New Item ${items.length + 1}`]);
};
const updateUser = () => {
setUser({
...user,
name: 'Jane'
});
};
return (
<div>
<h3>Name: {name}</h3>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
<h3>Age: {age}</h3>
<button onClick={() => setAge(age + 1)}>Increase Age</button>
<button onClick={() => setIsVisible(!isVisible)}>
{isVisible ? 'Hide' : 'Show'} Content
</button>
{isVisible && (
<div>
<h3>Item List:</h3>
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<button onClick={addItem}>Add Item</button>
</div>
)}
<h3>User: {user.name} ({user.email})</h3>
<button onClick={updateUser}>Update User</button>
</div>
);
}🔄 State Update Patterns
Functional Updates
jsx
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// Update based on previous state value
setCount(prevCount => prevCount + 1);
};
const incrementByFive = () => {
// Use functional updates for consecutive updates
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={incrementByFive}>+5</button>
</div>
);
}Object State Updates
jsx
function UserProfile() {
const [user, setUser] = useState({
name: '',
email: '',
age: 0,
preferences: {
theme: 'light',
language: 'en'
}
});
const updateName = (name) => {
setUser(prevUser => ({
...prevUser,
name
}));
};
const updateEmail = (email) => {
setUser(prevUser => ({
...prevUser,
email
}));
};
const updatePreferences = (newPrefs) => {
setUser(prevUser => ({
...prevUser,
preferences: {
...prevUser.preferences,
...newPrefs
}
}));
};
return (
<div>
<input
placeholder="Name"
value={user.name}
onChange={(e) => updateName(e.target.value)}
/>
<input
placeholder="Email"
value={user.email}
onChange={(e) => updateEmail(e.target.value)}
/>
<select
value={user.preferences.theme}
onChange={(e) => updatePreferences({ theme: e.target.value })}
>
<option value="light">Light Theme</option>
<option value="dark">Dark Theme</option>
</select>
<div>
<h3>User Info:</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Theme: {user.preferences.theme}</p>
</div>
</div>
);
}Array State Updates
jsx
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', completed: false },
{ id: 2, text: 'Write Code', completed: true }
]);
const [newTodo, setNewTodo] = useState('');
const addTodo = () => {
if (newTodo.trim()) {
setTodos(prevTodos => [
...prevTodos,
{
id: Date.now(),
text: newTodo,
completed: false
}
]);
setNewTodo('');
}
};
const toggleTodo = (id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
const deleteTodo = (id) => {
setTodos(prevTodos =>
prevTodos.filter(todo => todo.id !== id)
);
};
return (
<div>
<div>
<input
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add new task"
/>
<button onClick={addTodo}>Add</button>
</div>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}🎛️ Complex State Management
Using useReducer
jsx
import { useReducer } from 'react';
// Define reducer function
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
case 'set':
return { count: action.payload };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
}
function AdvancedCounter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>
+1
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
-1
</button>
<button onClick={() => dispatch({ type: 'reset' })}>
Reset
</button>
<button onClick={() => dispatch({ type: 'set', payload: 100 })}>
Set to 100
</button>
</div>
);
}Custom Hook for State Logic Encapsulation
jsx
// Custom Hook: Form state management
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = (name, value) => {
setValues(prev => ({
...prev,
[name]: value
}));
// Clear errors
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
const handleSubmit = (onSubmit, validationRules = {}) => {
return (e) => {
e.preventDefault();
const newErrors = {};
Object.keys(validationRules).forEach(field => {
const rule = validationRules[field];
if (rule.required && !values[field]) {
newErrors[field] = `${rule.label || field} is required`;
}
});
setErrors(newErrors);
if (Object.keys(newErrors).length === 0) {
onSubmit(values);
}
};
};
const reset = () => {
setValues(initialValues);
setErrors({});
};
return {
values,
errors,
handleChange,
handleSubmit,
reset
};
}
// Using custom Hook
function ContactForm() {
const { values, errors, handleChange, handleSubmit, reset } = useForm({
name: '',
email: '',
message: ''
});
const onSubmit = (formData) => {
console.log('Form submitted:', formData);
alert('Form submitted successfully!');
reset();
};
const validationRules = {
name: { required: true, label: 'Name' },
email: { required: true, label: 'Email' },
message: { required: true, label: 'Message' }
};
return (
<form onSubmit={handleSubmit(onSubmit, validationRules)}>
<div>
<input
placeholder="Name"
value={values.name}
onChange={(e) => handleChange('name', e.target.value)}
/>
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<input
type="email"
placeholder="Email"
value={values.email}
onChange={(e) => handleChange('email', e.target.value)}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<textarea
placeholder="Message"
value={values.message}
onChange={(e) => handleChange('message', e.target.value)}
/>
{errors.message && <span className="error">{errors.message}</span>}
</div>
<button type="submit">Submit</button>
<button type="button" onClick={reset}>Reset</button>
</form>
);
}⚡ State Performance Optimization
State Splitting
jsx
// ❌ All state in one object
function BadComponent() {
const [state, setState] = useState({
user: null,
posts: [],
loading: false,
theme: 'light',
sidebar: false
});
// Any field update causes the entire component to re-render
}
// ✅ Separate unrelated state
function GoodComponent() {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(false);
const [theme, setTheme] = useState('light');
const [sidebar, setSidebar] = useState(false);
// Each state updates independently, reducing unnecessary re-renders
}Using useMemo and useCallback for Optimization
jsx
import { useState, useMemo, useCallback } from 'react';
function OptimizedList({ items }) {
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('name');
// Cache filtered and sorted results
const filteredAndSortedItems = useMemo(() => {
const filtered = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return filtered.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return a[sortBy] - b[sortBy];
});
}, [items, filter, sortBy]);
// Cache event handlers
const handleFilterChange = useCallback((e) => {
setFilter(e.target.value);
}, []);
const handleSortChange = useCallback((e) => {
setSortBy(e.target.value);
}, []);
return (
<div>
<input
placeholder="Search..."
value={filter}
onChange={handleFilterChange}
/>
<select value={sortBy} onChange={handleSortChange}>
<option value="name">Sort by Name</option>
<option value="price">Sort by Price</option>
</select>
<ul>
{filteredAndSortedItems.map(item => (
<li key={item.id}>
{item.name} - ${item.price}
</li>
))}
</ul>
</div>
);
}📝 Chapter Summary
State management is the core of React applications. Properly using state enables building responsive and interactive user interfaces.
Key Takeaways
- ✅ useState Hook is used to manage component state
- ✅ State updates are asynchronous, use functional updates to ensure correctness
- ✅ Object and array state require immutable updates
- ✅ useReducer is suitable for complex state logic
- ✅ Custom Hooks can encapsulate state logic
- ✅ State splitting and caching can optimize performance
Best Practices
- Keep state minimal: Only store necessary state
- Flatten state structure: Avoid deep nesting
- Use functional updates: Ensure correctness of state updates
- Split state reasonably: Improve component performance
- Encapsulate complex logic: Use custom Hooks
Continue Learning: Next Chapter - React Event Handling