State is how components remember and respond to user interactions. Events let components react to user actions.
useState Hook
JSX
import { useState } from 'react';
function Counter() {
// [currentValue, setterFunction]
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(count - 1)}>-1</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}Updating State Correctly
JSX
function TodoApp() {
const [todos, setTodos] = useState([]);
// ✅ Correct: functional update (uses previous state)
const addTodo = (text) => {
setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);
};
// ✅ Correct: immutable update
const toggleTodo = (id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
};
// ✅ Correct: remove item
const removeTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
// ❌ Wrong: mutating state directly
// todos.push(newTodo); // Don't do this!
// todos[0].done = true; // Don't do this!
return (
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.done ? '✓' : '○'} {todo.text}
</li>
))}
</ul>
);
}Multiple State Updates
JSX
function UserForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: '',
});
// Single handler for multiple fields
const handleChange = (e) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value,
}));
};
// Or individual handlers
const setName = (name) => setFormData(prev => ({ ...prev, name }));
const setEmail = (email) => setFormData(prev => ({ ...prev, email }));
return (
<form>
<input name="name" value={formData.name} onChange={handleChange} />
<input name="email" value={formData.email} onChange={handleChange} />
<input name="age" value={formData.age} onChange={handleChange} />
</form>
);
}Event Handling
JSX
function InteractiveForm() {
const handleSubmit = (e) => {
e.preventDefault(); // Prevent page reload
console.log('Form submitted');
};
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
};
const handleChange = (e) => {
console.log('Target:', e.target.name, 'Value:', e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input
name="query"
onChange={handleChange}
onKeyDown={handleKeyDown}
/>
<button type="submit">Search</button>
</form>
);
}List Rendering
JSX
function ShoppingList() {
const [items, setItems] = useState([
{ id: 1, name: 'Milk', quantity: 2 },
{ id: 2, name: 'Bread', quantity: 1 },
{ id: 3, name: 'Eggs', quantity: 12 },
]);
const removeItem = (id) => {
setItems(prev => prev.filter(item => item.id !== id));
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} × {item.quantity}
<button onClick={() => removeItem(item.id)}>
✕
</button>
</li>
))}
</ul>
);
}Lifting State Up
JSX
// Shared state between siblings via parent
function TemperatureConverter() {
const [celsius, setCelsius] = useState('');
const fahrenheit = celsius ? (parseFloat(celsius) * 9/5) + 32 : '';
return (
<div>
<TemperatureInput
scale="Celsius"
value={celsius}
onChange={setCelsius}
/>
<TemperatureInput
scale="Fahrenheit"
value={fahrenheit}
onChange={(f) => setCelsius(((parseFloat(f) - 32) * 5/9).toFixed(1))}
/>
</div>
);
}
function TemperatureInput({ scale, value, onChange }) {
return (
<fieldset>
<legend>Enter temperature in {scale}:</legend>
<input
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</fieldset>
);
}💡 Tip: State should live as close to where it's used as possible. If two components need the same state, move it to their closest common parent.
Next: Hooks Deep Dive