State and Events

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