React

Chapter 11: Custom Hooks — Extracting and sharing stateful logic, Rules of Hooks, useFetch, useLocalStorage, useWindowDimensions, and useDebounce.

1. What is a Custom Hook?

A Custom Hook is a regular JavaScript function whose name starts with use (e.g., useFetch, useLocalStorage) and that can call other built-in React hooks like useState, useEffect, or useRef.

Custom hooks allow you to extract complex stateful logic from your components so that multiple components can share the same behavior without duplicating code or modifying the UI tree.

💡 Real-World Analogy: Modular Power Tool Attachments

Think of your component as a cordless power tool:

  • Instead of building a separate drill motor, sander motor, and saw motor from scratch every time, you have plug-in attachments (Custom Hooks): useDrill(), useSander(), useSaw().
  • Any component in your app can snap on the attachment, immediately gain all the specialized mechanics (state & effects), and output its own unique woodwork (UI)!

2. The 3 Golden Rules of Hooks

01

1. Top-Level Only

Never call hooks inside loops, conditions (if/else), or nested functions. Hooks must execute in the exact same order on every render.

02

2. React Functions Only

Only call hooks from React Functional Components or from other Custom Hooks (never from regular JS helper functions).

03

3. 'use' Prefix Name

Always start custom hook names with use (e.g. useAuth). This signals React's ESLint plugins to enforce hook safety rules.

3. Essential Real-World Custom Hook Examples

1. useLocalStorage: Syncing State with Browser Storage

function useLocalStorage(key, initialValue) {
    // 1. Lazy state initialization: read once from localStorage
    const [storedValue, setStoredValue] = React.useState(() => {
        try {
            const item = window.localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
        } catch (error) {
            console.error(error);
            return initialValue;
        }
    });

    // 2. Wrap setter to persist changes to localStorage automatically
    const setValue = (value) => {
        try {
            const valueToStore = value instanceof Function ? value(storedValue) : value;
            setStoredValue(valueToStore);
            window.localStorage.setItem(key, JSON.stringify(valueToStore));
        } catch (error) {
            console.error(error);
        }
    };

    return [storedValue, setValue];
}

// Usage in any component:
function ThemeSelector() {
    const [theme, setTheme] = useLocalStorage("app_theme", "dark");
    return <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>Theme: {theme}</button>;
}
LocalStorage Synchronization Output:
[useLocalStorage] Read "app_theme" from window.localStorage -> "dark" [Action] User clicked Theme button -> window.localStorage.setItem("app_theme", '"light"') [State] Component re-rendered with theme: "light" (Persists on page reload!)

2. useWindowDimensions: Live Viewport Resize Tracker

function useWindowDimensions() {
    const [dimensions, setDimensions] = React.useState({
        width: window.innerWidth,
        height: window.innerHeight
    });

    React.useEffect(() => {
        function handleResize() {
            setDimensions({ width: window.innerWidth, height: window.innerHeight });
        }

        window.addEventListener("resize", handleResize);
        return () => window.removeEventListener("resize", handleResize);
    }, []);

    return dimensions;
}
Window Dimensions Preview:
📐 1440px × 900px (Updates dynamically on window resize)

4. Hands-On Practice Challenges

Master custom hook construction, debouncers, and hook rule validation. Click "Solve Challenge" to open the code workspace!

🎯 Hands-on Coding Challenge Custom Hook Design

Challenge 1: Reusable Boolean useToggle Hook

Implement a custom hook factory createCustomToggleHook(initialState) that returns getValue(), toggle(), setTrue(), and setFalse().

📋 Requirements & What to Write:
  • Encapsulate boolean state initialized with Boolean(initialState).
  • toggle() flips state, setTrue() forces true, setFalse() forces false.
  • Return the methods in an object interface.
Expected Result: Initial: false | After toggle: true | Force false: false
💡
Editor Instructions: Complete createCustomToggleHook and verify all toggle operations.
useToggle.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Debouncing Hooks

Challenge 2: useDebounce Search Query Simulator

Implement a simulateDebouncedHook(initialValue, delayMs) function that tracks both raw input state and debounced values for high-frequency search bars.

📋 Requirements & What to Write:
  • Track currentValue and debouncedValue.
  • setQuery(newVal) updates and returns { raw, debounced }.
  • getDebouncedValue() returns the debounced string.
Expected Result: Updated: { raw: "react 18 hooks", debounced: "react 18 hooks" }
💡
Editor Instructions: Build the state tracker for debounced values.
useDebounce.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Rules of Hooks Linter

Challenge 3: Rules of Hooks Linter & Auditor

Write an automated rule auditor validateHookRules(functionCodeString) that verifies hooks are not enclosed within if branches or for loops and adhere to the use naming convention.

📋 Requirements & What to Write:
  • Scan for hooks inside conditional if blocks.
  • Scan for hooks inside loops (for, while).
  • Verify function name starts with use or a capital letter (component).
Expected Result: Audit Bad: ["VIOLATION: Hook called inside conditional if-statement.", ...] | Audit Good: "✅ Valid Hook Architecture!"
💡
Editor Instructions: Complete validateHookRules and click "▶ Run Code".
hookAuditor.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...