2 REACT FRONTEND

Advanced Hooks

🧰 Advanced Hooks: Beyond State and Effects

useState and useEffect will cover 80% of your day-to-day React engineering. However, as your application scales, managing state becomes exponentially more difficult.

What happens when 50 components all need to know if the User is logged in? What happens when a form has 20 interdependent fields? How do you smoothly focus an input box without re-rendering the whole page?

We solve these architectural problems using Advanced Hooks.


1️⃣ The Prop Drilling Problem and useContext

Prop Drilling is the painful process of passing data through 5 layers of components that do not need the data, simply to get it to the 6th layer component that does need it.

Here, Layout, Sidebar, and SettingsMenu have absolutely zero use for the 'user' variable, but they are polluted with it just to pass it downward.

Enter useContext (The Teleporter)

Context allows you to teleport data from the very top of your application directly into any component at the very bottom, completely bypassing the middle layers.

Step 1: Create and Provide the Context (At the Top)

import { createContext, useState } from 'react';

// Generate the empty portal
export const UserContext = createContext();

function App() {
    const [user, setUser] = useState({ name: "Mwero", role: "Admin" });

    return (
        // Wrapping the app in a "Provider" beams the data downward
        <UserContext.Provider value={user}>
            <Layout /> 
        </UserContext.Provider>
    );
}

Step 2: Consume the Context (At the Bottom)

import { useContext } from 'react';
import { UserContext } from './App'; // Import the portal

function Avatar() {
    // Magically reach up and grab the data directly from the Provider!
    const user = useContext(UserContext);

    return <img alt={user.name} src="/avatar.jpg" />;
}

2️⃣ Complex State Math: useReducer

useState is perfect for simple strings and booleans (isOpen, count). But what if your state is a massive shopping cart object, and you need to handle ADD_ITEM, REMOVE_ITEM, APPLY_DISCOUNT, and CLEAR_CART?

useReducer is a more powerful, structured alternative to useState. It forces you to define all possible state changes in one centralized mathematical function (The Reducer).

import { useReducer } from 'react';

// 1. The pure mathematical function that calculates exactly HOW state changes.
// It takes the current cart, an Action object, and returns the strictly new cart.
function cartReducer(state, action) {
    switch (action.type) {
        case 'ADD_ITEM':
            return { total: state.total + action.payload.price };
        case 'APPLY_DISCOUNT':
            return { total: state.total * 0.9 };
        case 'CLEAR':
            return { total: 0 };
        default:
            return state; // If unknown command, do nothing.
    }
}

function ShoppingCart() {
    // 2. Initialize the Hook
    const [cart, dispatch] = useReducer(cartReducer, { total: 0 });

    return (
        <div>
            <h1>Total: ${cart.total}</h1>
            
            {/* 3. "Dispatch" commands (Actions) to the Reducer */}
            <button onClick={() => dispatch({ type: 'ADD_ITEM', payload: { price: 50 } })}>
                Add $50 Item
            </button>
            
            <button onClick={() => dispatch({ type: 'APPLY_DISCOUNT' })}>
                Apply 10% Off
            </button>
            
            <button onClick={() => dispatch({ type: 'CLEAR' })}>
                Empty Cart
            </button>
        </div>
    );
}

Note: useReducer is the foundational concept behind Redux, the world's most popular enterprise state management library.


3️⃣ Bypassing the Render Cycle: useRef

Sometimes, you need to store a variable that changes, but you absolutely DO NOT want the page to re-render when it changes.

If you use useState to track how many times a user clicks a button, the entire component redraws every single click. If you use a regular let x = 0; variable, React destroys the variable and resets it to 0 every time the component re-renders for other reasons.

useRef as a Persistent Mutable Variable

useRef is a box that survives re-renders, and mutating it is completely silent.

import { useRef, useState } from 'react';

function StopWatch() {
    const [seconds, setSeconds] = useState(0);
    
    // Stores the exact ID of the background timer silently
    const timerId = useRef(null);

    const start = () => {
        // We do NOT want setting the ID to trigger a UI render.
        timerId.current = setInterval(() => setSeconds(s => s + 1), 1000);
    };

    const stop = () => {
        // We can safely grab the ID from the ref without wiping it out.
        clearInterval(timerId.current);
    };

    return <div>{seconds}s <button onClick={start}>Start</button></div>;
}

useRef as a direct DOM Manipulator

In Vanilla JS, you do document.getElementById(). In React, manipulating the DOM directly is illegal because React's Virtual DOM handles it for you. However, you can use a Ref to safely grab a specific HTML node (like an <input>) to perform native methods like focus().

function FocusInput() {
    // 1. Create the empty Ref
    const inputRef = useRef(null);

    const forceFocus = () => {
        // 3. Directly poke the physical underlying HTML <input> element.
        inputRef.current.focus();
    };

    return (
        <div>
            {/* 2. Physically attach the Ref to the exact DOM element */}
            <input ref={inputRef} placeholder="Wait to type..." />
            <button onClick={forceFocus}>Focus the Box!</button>
        </div>
    );
}

4️⃣ Performance Hooks: useMemo and useCallback

React re-renders components aggressively. If <App /> changes state, every single child component inside it is destroyed and redrawn from scratch.

Usually, React is fast enough that you don't notice. But if a child component does massive internal math calculations (like filtering 10,000 database rows), redrawing it 60 times a second will freeze the browser.

useMemo (Memorizing Calculated Values)

"Memoization" is mathematical caching. It remembers the answer to a math problem so it doesn't have to calculate it again unless the inputs change.

// This 5-second calculation ONLY runs when 'usersArray' or 'searchTerm' changes.
// It will instantly return the cached answer if other generic state changes in the app.
const filteredUsers = useMemo(() => {
    return usersArray.filter(user => user.name.includes(searchTerm));
}, [usersArray, searchTerm]);

useCallback (Memorizing Functions)

Every time a component re-renders, React literally deletes and recreates every single function inside it. If you pass a function down as a Prop, the child component sees a "brand new" function and is forced to violently re-render itself. useCallback caches the function itself in memory.


💡 Summary Hierarchy of Hooks

HookPurposeWhen to use it over useState
useStateSimple Local VariablesThe default. A string, int, or simple array.
useContextTeleporting VariablesWhen you are passing props down through >3 unrelated layers.
useReducerComplex State LogicWhen state is an object requiring 4+ different math commands (Add, Remove, Reset).
useRefSilent PersistenceWhen data changes rapidly but shouldn't trigger a visual screen update (like a timer ID).

Knowledge Check

Complete this quick quiz to verify your understanding and unlock the next module.