MODULE 5
State & Events
Make your components interactive with state and event handling
Understanding State
State is data that changes over time. When state changes, React re-renders the component to reflect the new data.
Props vs State
- Props: Read-only data from parent. Don't change.
- State: Mutable data owned by component. Can change.
State Examples
- Counter value that increments when button is clicked
- Form input values that change as user types
- Loading status (loading, success, error)
- Modal visibility (open/closed)
- Current user authentication status
useState Hook
The useState hook is the primary way to add state to functional components.
Basic Syntax
import { useState } from 'react';
function Counter() {
// Declare state variable
const [count, setCount] = useState(0);
// count = current value
// setCount = function to update value
// 0 = initial value
return (
Count: {count}
);
}Multiple State Variables
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState(0);
return (
Name: {name}
Email: {email}
Age: {age}
);
}Complex State (Objects)
function UserProfile() {
const [user, setUser] = useState({
name: 'Alice',
email: 'alice@example.com',
age: 25
});
// Update entire object
const updateUser = (newUser) => {
setUser(newUser);
};
// Update one property (immutably)
const updateName = (newName) => {
setUser({ ...user, name: newName });
};
return (
{user.name} ({user.email})
);
}
💡 Remember: Always treat state as immutable. Create a new object instead of modifying the existing one.
Event Handling
React events are similar to HTML events but with camelCase names.
Common Events
// Click // Change // Submit
Event Handlers
// Arrow function (preferred)
function Button() {
const handleClick = () => {
console.log('Clicked!');
};
return ;
}
// Inline arrow function
function Button() {
return ;
}
// Passing arguments
function Button() {
const handleClick = (id) => {
console.log('Clicked item:', id);
};
return ;
}Access Event Object
function Form() {
const handleChange = (e) => {
console.log(e.target.value); // Get input value
console.log(e.target.type); // Get input type
console.log(e.key); // For keyboard events
};
return ;
}Controlled Inputs
Controlled inputs are connected to state. React controls the input value.
Controlled Input Example
function SearchBox() {
const [searchTerm, setSearchTerm] = useState('');
return (
setSearchTerm(e.target.value)}
placeholder="Search..."
/>
Searching for: {searchTerm}
);
}Why Controlled Inputs Matter
- React always knows the current input value
- Easy to validate or filter input
- Can clear input programmatically
- Can disable submit based on input state
Forms
Handling Form Submission
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); // Prevent page reload
console.log('Logging in as:', username);
// Send to server, etc.
};
return (
);
}Multiple Form Fields
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value // Update specific field
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted:', formData);
};
return (
);
}Lifting State Up
When multiple components need to share state, move it to their common parent.
Problem: Sibling Components Need to Communicate
// ❌ This doesn't work - siblings can't communicate directly
function Parent() {
return (
);
}Solution: Lift State to Parent
function Parent() {
const [temperature, setTemperature] = useState(0);
return (
setTemperature(newTemp)}
/>
);
}
function TemperatureInput({ value, onChange }) {
return (
onChange(Number(e.target.value))}
type="number"
/>
);
}
function TemperatureDisplay({ value }) {
return Current temperature: {value}°C
;
}Derived State
Calculate values from existing state instead of storing them separately.
Good Practice
function ShoppingCart({ items }) {
// Don't store total as state - derive it
const total = items.reduce((sum, item) => sum + item.price, 0);
return Total: ${total.toFixed(2)}
;
}Avoid Redundant State
// ❌ Bad: storing derived data
function User({ firstName, lastName }) {
const [fullName, setFullName] = useState(firstName + ' ' + lastName);
// What if firstName changes? fullName is out of sync!
return {fullName}
;
}
// ✅ Good: calculate when needed
function User({ firstName, lastName }) {
const fullName = firstName + ' ' + lastName;
return {fullName}
;
}Common Patterns
Toggle Boolean
function Modal() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
{isOpen && Modal content}
>
);
}List Management
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
const newTodo = { id: Date.now(), text: input };
setTodos([...todos, newTodo]);
setInput('');
};
const removeTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
return (
<>
setInput(e.target.value)} />
-
{todos.map(todo => (
- {todo.text} ))}
Key Takeaways
- State is mutable data managed by a component
- Use
useStateto add state to functional components - Treat state as immutable—create new objects/arrays
- Event handlers respond to user interactions
- Controlled inputs are connected to state
- Prevent default behavior with
e.preventDefault() - Lift state to parent when sibling components need to share data
- Derive values instead of storing redundant state
Practice Exercise
- Create a counter with increment/decrement buttons
- Build a form that collects multiple inputs
- Create a todo list with add/remove functionality
- Build a toggle component (like a menu)
- Create a temperature converter with lifted state