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

8 min read

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:

  1. State changes - via setState
  2. Props change - parent passes new props
  3. Parent re-renders - children re-render by default
  4. 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

  1. Never Mutate State Directly
// ❌ WRONG
state.value = newValue;
array.push(newItem);

// ✅ CORRECT
setState(newValue);
setArray([...array, newItem]);
  1. 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
  1. 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 useState to 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 useEffect hook 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


Remember: State management is the heart of React interactivity. Master these concepts, and you’re well on your way to building amazing React applications! 🚀

Back to blog