React

Chapter 04: Events & State Management — The useState hook, SyntheticEvent system, immutability, functional updates, and React 18 batching.

1. What is State?

State is a component's internal memory. It holds data that can change over time based on user interactions (typing in an input, clicking a button, fetching server data) and whose modification causes React to automatically re-render the component with updated UI.

💡 Real-World Analogy: Whiteboard vs Snapshot Polaroid

Think of your component as a room:

  • Props: A permanent Polaroid photo handed to you from outside. You can look at it, but you cannot draw on it or change it.
  • State: A living whiteboard mounted on the wall inside the room. You can write on it, erase it, and whenever you change the numbers on the whiteboard, everyone in the room looks at it and updates their notes (re-render)!

Why Regular Variables Fail Inside Components

// ❌ REGULAR VARIABLE (Does NOT trigger a re-render!):
function CounterBroken() {
    let count = 0; // Reset back to 0 on every render!

    function handleClick() {
        count++; // Variable changes, but React doesn't know to update the screen!
        console.log("Count:", count);
    }

    return <button onClick={handleClick}>Count: {count}</button>;
}

// ✅ REACT STATE (useState triggers reconciliation & persists across renders):
function CounterWorking() {
    const [count, setCount] = React.useState(0);

    return (
        <button onClick={() => setCount(count + 1)}>
            Count: {count}
        </button>
    );
}
Interactive UI Output:
← Each click dispatches setCount, updating UI without page reload.

2. The useState Hook Syntax

The useState hook is a built-in React function that returns an array with exactly two elements: [currentValue, setterFunction].

useState Array Destructuring Anatomy
const [ count , setCount ] = useState ( 0 ); ① Current State Value ② Setter Function ③ Initial State

3. Handling Events in React (SyntheticEvent System)

React wraps native browser events in a cross-browser compatible SyntheticEvent wrapper with unified performance and event pooling features.

01

HTML Event vs React Event

  • HTML: Lowercase string: onclick="handleClick()".
  • React: camelCase function reference: onClick={handleClick}.
  • Prevent Default: In React, you must explicitly call e.preventDefault() rather than returning false.
02

Passing Custom Arguments to Handlers

Wrap the call in an inline arrow function to avoid executing the handler immediately on initial render:

// ✅ Pass arguments via arrow function:
<button onClick={() => handleDelete(user.id)}>
    Delete
</button>

// ❌ WRONG: Executes immediately when rendered!
<button onClick={handleDelete(user.id)}>

4. State Immutability: Updating Objects and Arrays

React compares previous and new state by shallow reference equality (Object.is()). If you mutate an object or array in place (e.g. user.name = "Alex" or items.push(newItem)), its memory reference does not change, and React will skip re-rendering!

⚠️ Always Use the Spread Operator (...) to Create New Shallow Copies

// 1. UPDATING AN OBJECT IN STATE:
const [user, setUser] = React.useState({ name: "Anshuman", role: "Dev", age: 22 });

// ❌ WRONG: Mutates state directly (No re-render!)
user.age = 23;
setUser(user);

// ✅ CORRECT: Copy existing properties with spread operator (...) and override changed fields:
setUser({
    ...user,
    age: 23
});

// 2. UPDATING AN ARRAY IN STATE:
const [todos, setTodos] = React.useState(["Learn JS", "Learn React"]);

// ❌ WRONG: Array.prototype.push mutates existing array:
// todos.push("Learn Redux");

// ✅ CORRECT: Return a brand-new array using spread:
setTodos([...todos, "Learn Redux"]);

// ✅ CORRECT FILTER (Deletion):
setTodos(todos.filter(todo => todo !== "Learn JS"));
Immutable State Transformation Output:
// Prev User Reference: 0x7ffd19 // Next User Reference: 0x811a4b (New Object created via {...user}) // Result: React detects reference change and re-renders successfully!

5. Functional Updates & React 18 Automatic Batching

Functional State Updates: setCount(prev => prev + 1)

When your new state depends on the previous state value, always pass an updater callback: setCount(prev => prev + 1). This guarantees you are reading the most up-to-date state even during batched asynchronous updates:

function MultiCounter() {
    const [count, setCount] = React.useState(0);

    function handleTripleIncrement() {
        // ❌ WRONG: All 3 calls read the same snapshot value of `count` (0). Result is 1, not 3!
        // setCount(count + 1);
        // setCount(count + 1);
        // setCount(count + 1);

        // ✅ CORRECT: Updater function receives latest pending state:
        setCount(prev => prev + 1); // 0 -> 1
        setCount(prev => prev + 1); // 1 -> 2
        setCount(prev => prev + 1); // 2 -> 3 (Final Count: 3!)
    }

    return <button onClick={handleTripleIncrement}>+3 (Count: {count})</button>;
}

⚡ React 18 Automatic Batching

In React 18+, multiple state updates inside promises, timeouts, or native event handlers are automatically batched together into a single re-render. This prevents unnecessary intermediate paints and dramatically boosts UI performance.

6. Top Interview Questions & Common Pitfalls

⚠️ Pitfall 1: "Why does console.log(count) show the old value right after setCount(count + 1)?"

The Explanation: Calling a state setter does NOT immediately update the variable in the current execution block! Setting state schedules a future re-render. Inside the current event handler, count remains a constant snapshot of the current render frame.

⚠️ Pitfall 2: "What causes the 'Too many re-renders' infinite loop error?"

The Explanation: Writing <button onClick={setCount(count + 1)}> executes setCount during the component's render phase. Setting state triggers a re-render, which immediately calls setCount again, leading to an infinite render crash. Always pass a function reference: onClick={() => setCount(count + 1)}.

7. Hands-On Practice Challenges

Master state management, updater functions, and immutable updates with these coding challenges. Click "Solve Challenge" to open the editor!

🎯 Hands-on Coding Challenge Hook Internals

Challenge 1: Recreate useState with Functional Updaters

Write a closure function createMiniUseState(initialValue) that supports both direct values (setState(10)) and updater functions (setState(prev => prev + 1)).

📋 Requirements & What to Write:
  • Store state in closure scope initialized to initialValue.
  • If newValue is a function, execute newValue(state).
  • Return tuple [getState, setState].
Expected Result: Final Simulated State: 6
💡
Editor Instructions: Complete createMiniUseState with functional updater support.
miniUseState.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Reducers & Immutability

Challenge 2: Immutable State Reducer Simulator

Implement a pure state update function updateImmutableState(state, action) that adds and removes items from an array without mutating the original state object.

📋 Requirements & What to Write:
  • Handle ADD_ITEM using array spread [...state.items, action.payload].
  • Handle REMOVE_ITEM using state.items.filter(...).
  • Never mutate state.items directly with push or splice.
Expected Result: Original items length: 1 | New state items length: 2
💡
Editor Instructions: Return a new object with spread operator for all actions.
immutableReducer.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Batching Engine

Challenge 3: Simulate React 18 State Batching Queue

Write a function simulateBatchedUpdates(initialVal, updateQueue) that takes an initial number and an array of queued state updates (mixing functions and direct values) and returns the final consolidated result.

📋 Requirements & What to Write:
  • Iterate through updateQueue sequentially.
  • If an item is a function, execute with current accumulator: item(current).
  • If an item is a direct value, replace accumulator directly.
Expected Result: Final Result: 60
💡
Editor Instructions: Sequentially process functions and direct values in the update queue.
batchingQueue.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...