React Blitz - Day 2: State, Events & React's Rendering Model
Master React state management and event handling in 60 minutes - learn useState, interactive forms, and React's rendering behavior with practical examples
Duration: 1 hour
Introduction (2 min)
Welcome to Day 2 of your React journey! Today we’re diving into the heart of React’s interactivity. By the end of this hour, you’ll understand how to make your components come alive with state and handle user interactions like a pro.
Part 1: Theory - Understanding State and Events (20 min)
What is State? (5 min)
State is data that changes over time in your application. Think of it as your component’s memory - it remembers things between renders.
import { useState } from 'react';
function Counter() {
// useState returns an array with two items:
// 1. The current state value
// 2. A function to update that value
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
{/* When button is clicked, update the state */}
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Key Points:
- State is local and isolated to each component instance
- When state changes, React re-renders the component
- State updates are asynchronous - React batches them for performance
useState Hook Deep Dive (5 min)
// Basic usage with primitive values
const [text, setText] = useState(''); // string
const [count, setCount] = useState(0); // number
const [isVisible, setIsVisible] = useState(false); // boolean
// Using objects as state
const [user, setUser] = useState({
name: 'John',
age: 30
});
// ⚠️ WRONG - Don't mutate state directly
user.name = 'Jane'; // This won't trigger re-render!
// ✅ CORRECT - Create a new object
setUser({
...user, // spread existing properties
name: 'Jane' // override the name
});
// Functional updates - when new state depends on previous state
setCount(prevCount => prevCount + 1); // Always use previous value
Event Handling in React (5 min)
React uses synthetic events - a cross-browser wrapper around native events.
function FormExample() {
const [inputValue, setInputValue] = useState('');
// Event handler function
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); // Prevent default form submission
console.log('Submitted:', inputValue);
};
// Inline event handler with parameters
const handleButtonClick = (id: number) => {
console.log('Button clicked with ID:', id);
};
return (
<form onSubmit={handleSubmit}>
{/* Controlled input - value is controlled by React state */}
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
{/* Different ways to attach event handlers */}
<button onClick={() => handleButtonClick(1)}>
Button 1
</button>
<button type="submit">Submit</button>
</form>
);
}
Understanding React’s Rendering Model (5 min)
React re-renders a component when:
- State changes - via setState
- Props change - parent passes new props
- Parent re-renders - children re-render by default
- Context changes - if component uses that context
function Parent() {
const [parentCount, setParentCount] = useState(0);
console.log('Parent rendered'); // This logs on every render
return (
<div>
<button onClick={() => setParentCount(parentCount + 1)}>
Parent Count: {parentCount}
</button>
{/* Child will re-render when Parent re-renders */}
<Child name="React" />
</div>
);
}
function Child({ name }: { name: string }) {
console.log('Child rendered'); // Logs even if props didn't change
return <p>Hello, {name}!</p>;
}
Part 2: Practice - Building Interactive Components (35 min)
Exercise 1: Interactive Task List (20 min)
Let’s build a fully functional task list with add, delete, and toggle functionality.
import { useState } from 'react';
// Define the shape of a task
interface Task {
id: number;
text: string;
completed: boolean;
}
function TaskList() {
// State for all tasks
const [tasks, setTasks] = useState<Task[]>([]);
// State for the input field
const [inputText, setInputText] = useState('');
// Counter for unique IDs
const [nextId, setNextId] = useState(0);
// Add a new task
const handleAddTask = (e: React.FormEvent) => {
e.preventDefault(); // Prevent form submission
// Don't add empty tasks
if (inputText.trim() === '') return;
// Create new task object
const newTask: Task = {
id: nextId,
text: inputText,
completed: false
};
// Add task to state (creating new array)
setTasks([...tasks, newTask]);
// Clear input field
setInputText('');
// Increment ID for next task
setNextId(nextId + 1);
};
// Toggle task completion status
const handleToggleTask = (id: number) => {
// Map through tasks and toggle the matching one
setTasks(tasks.map(task =>
task.id === id
? { ...task, completed: !task.completed } // Create new object with toggled status
: task // Keep other tasks unchanged
));
};
// Delete a task
const handleDeleteTask = (id: number) => {
// Filter out the task with matching ID
setTasks(tasks.filter(task => task.id !== id));
};
// Calculate statistics
const completedCount = tasks.filter(task => task.completed).length;
const totalCount = tasks.length;
return (
<div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto' }}>
<h1>📝 Task List</h1>
{/* Statistics */}
<div style={{ marginBottom: '20px' }}>
<strong>Progress: </strong>
{completedCount} / {totalCount} completed
{totalCount > 0 && (
<span> ({Math.round((completedCount / totalCount) * 100)}%)</span>
)}
</div>
{/* Add task form */}
<form onSubmit={handleAddTask} style={{ marginBottom: '20px' }}>
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Enter a new task..."
style={{
padding: '8px',
marginRight: '8px',
width: '300px'
}}
/>
<button type="submit" style={{ padding: '8px 16px' }}>
Add Task
</button>
</form>
{/* Task list */}
<ul style={{ listStyle: 'none', padding: 0 }}>
{tasks.map(task => (
<li
key={task.id}
style={{
marginBottom: '10px',
padding: '10px',
border: '1px solid #ddd',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
backgroundColor: task.completed ? '#f0f0f0' : 'white'
}}
>
{/* Checkbox to toggle completion */}
<input
type="checkbox"
checked={task.completed}
onChange={() => handleToggleTask(task.id)}
style={{ marginRight: '10px' }}
/>
{/* Task text with strikethrough if completed */}
<span
style={{
flex: 1,
textDecoration: task.completed ? 'line-through' : 'none',
color: task.completed ? '#888' : '#000'
}}
>
{task.text}
</span>
{/* Delete button */}
<button
onClick={() => handleDeleteTask(task.id)}
style={{
padding: '4px 8px',
backgroundColor: '#ff4444',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Delete
</button>
</li>
))}
</ul>
{/* Empty state message */}
{tasks.length === 0 && (
<p style={{ textAlign: 'center', color: '#888' }}>
No tasks yet. Add one above! 🚀
</p>
)}
</div>
);
}
Exercise 2: Advanced Counter with Multiple Controls (15 min)
Build a counter with various control options to demonstrate different state update patterns.
import { useState } from 'react';
function AdvancedCounter() {
// Main counter state
const [count, setCount] = useState(0);
// Step size for increment/decrement
const [stepSize, setStepSize] = useState(1);
// Input field for direct value setting
const [inputValue, setInputValue] = useState('');
// Increment using functional update to ensure correct value
const handleIncrement = () => {
setCount(prevCount => prevCount + stepSize);
};
// Decrement using functional update
const handleDecrement = () => {
setCount(prevCount => prevCount - stepSize);
};
// Reset to zero
const handleReset = () => {
setCount(0);
setInputValue(''); // Also clear the input field
};
// Set counter to specific value from input
const handleSetValue = (e: React.FormEvent) => {
e.preventDefault();
const newValue = parseInt(inputValue);
// Validate input is a number
if (!isNaN(newValue)) {
setCount(newValue);
setInputValue(''); // Clear input after setting
}
};
// Handle step size change
const handleStepChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
setStepSize(parseInt(e.target.value));
};
// Multiple sequential updates (demonstrates batching)
const handleMultipleUpdates = () => {
// React batches these updates - only one re-render!
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);
// Final result: count + 3
};
return (
<div style={{
padding: '20px',
maxWidth: '400px',
margin: '0 auto',
textAlign: 'center'
}}>
<h1>🔢 Advanced Counter</h1>
{/* Display current count */}
<div style={{
fontSize: '48px',
fontWeight: 'bold',
margin: '20px 0',
color: count < 0 ? '#ff4444' : count > 0 ? '#44ff44' : '#000'
}}>
{count}
</div>
{/* Step size selector */}
<div style={{ marginBottom: '20px' }}>
<label>
Step Size:
<select
value={stepSize}
onChange={handleStepChange}
style={{ marginLeft: '10px', padding: '4px' }}
>
<option value="1">1</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="25">25</option>
</select>
</label>
</div>
{/* Control buttons */}
<div style={{ marginBottom: '20px' }}>
<button
onClick={handleDecrement}
style={{
padding: '10px 20px',
margin: '0 5px',
fontSize: '16px'
}}
>
- {stepSize}
</button>
<button
onClick={handleReset}
style={{
padding: '10px 20px',
margin: '0 5px',
fontSize: '16px'
}}
>
Reset
</button>
<button
onClick={handleIncrement}
style={{
padding: '10px 20px',
margin: '0 5px',
fontSize: '16px'
}}
>
+ {stepSize}
</button>
</div>
{/* Direct value input */}
<form onSubmit={handleSetValue} style={{ marginBottom: '20px' }}>
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter value..."
style={{
padding: '8px',
marginRight: '8px',
width: '150px'
}}
/>
<button type="submit" style={{ padding: '8px 16px' }}>
Set Value
</button>
</form>
{/* Demonstrate batching */}
<button
onClick={handleMultipleUpdates}
style={{
padding: '8px 16px',
backgroundColor: '#4444ff',
color: 'white',
border: 'none',
borderRadius: '4px'
}}
>
Add 3 (Batched Updates) 🎯
</button>
{/* Info section */}
<div style={{
marginTop: '30px',
padding: '15px',
backgroundColor: '#f5f5f5',
borderRadius: '8px',
textAlign: 'left'
}}>
<h3>💡 What's happening?</h3>
<ul style={{ fontSize: '14px' }}>
<li>Each button click updates state</li>
<li>Functional updates ensure correct values</li>
<li>React batches multiple setState calls</li>
<li>Component re-renders after state changes</li>
</ul>
</div>
</div>
);
}
Part 3: Quick Review - Key Concepts (5 min)
The Golden Rules of State
- Never Mutate State Directly
// ❌ WRONG
state.value = newValue;
array.push(newItem);
// ✅ CORRECT
setState(newValue);
setArray([...array, newItem]);
- Use Functional Updates for Dependent State
// ❌ RISKY - might use stale value
setCount(count + 1);
setCount(count + 1); // Still uses old count!
// ✅ SAFE - always uses current value
setCount(prev => prev + 1);
setCount(prev => prev + 1); // Uses updated value
- State Updates are Asynchronous
setState(newValue);
console.log(state); // Still shows old value!
// Use useEffect to react to state changes
useEffect(() => {
console.log('State updated:', state);
}, [state]);
Common Patterns Quick Reference
// Toggle boolean
setIsOpen(prev => !prev);
// Update object property
setPerson(prev => ({
...prev,
name: 'New Name'
}));
// Add to array
setItems(prev => [...prev, newItem]);
// Remove from array
setItems(prev => prev.filter(item => item.id !== targetId));
// Update array item
setItems(prev => prev.map(item =>
item.id === targetId ? { ...item, ...updates } : item
));
Checkpoint: What You Should Know Now ✅
After this lesson, you should be able to:
- ✅ Use
useStateto manage component state - ✅ Handle user events (clicks, form submissions, inputs)
- ✅ Update state based on previous state using functional updates
- ✅ Understand when and why components re-render
- ✅ Create controlled form inputs
- ✅ Manage arrays and objects in state properly
- ✅ Avoid common state mutation pitfalls
What’s Coming Tomorrow?
Day 3: Side Effects and Data Fetching
- The
useEffecthook for side effects - Fetching data from APIs
- Loading states and error handling
- Cleanup functions and dependencies
Homework Challenge 🏆
Combine what you’ve learned! Create a “Shopping Cart” component with:
- Add items with name and quantity
- Update quantities
- Remove items
- Calculate total items
- Clear entire cart
- Show “empty cart” message when no items
This will reinforce all concepts from today’s lesson!
Additional Resources
- React Docs: State
- React Docs: Responding to Events
- React Docs: Updating Objects in State
- React Docs: Updating Arrays in State
Remember: State management is the heart of React interactivity. Master these concepts, and you’re well on your way to building amazing React applications! 🚀