SyntaxStudy
Sign Up
React Building Custom Hooks
React Beginner 1 min read

Building Custom Hooks

A custom hook is a plain JavaScript function whose name starts with use and that may call other hooks inside it. There is no special API to register it — the naming convention is all that distinguishes it from a regular helper function. The convention matters because it signals to linters and developers that the function follows the rules of hooks and must only be called at the top level of a component or another hook. Custom hooks are the primary mechanism for sharing stateful logic between components. Before hooks, this required higher-order components or render props, both of which add nesting and complicate the component tree. A custom hook collocates the logic and exposes only the values the caller needs, keeping components focused on rendering. Good custom hooks are narrow in scope, like any good function. A useFetch hook should fetch data and report loading and error state. A useDebounce hook should debounce a value. Combining many concerns into one hook reduces reusability and makes the hook harder to test in isolation.
Example
import { useState, useEffect } from 'react';

// ── useLocalStorage ────────────────────────────────────────────────────────
function useLocalStorage(key, initialValue) {
    const [storedValue, setStoredValue] = useState(() => {
        try {
            const item = window.localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
        } catch {
            return initialValue;
        }
    });

    function setValue(value) {
        const valueToStore = value instanceof Function ? value(storedValue) : value;
        setStoredValue(valueToStore);
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
    }

    return [storedValue, setValue];
}

// ── useDebounce ────────────────────────────────────────────────────────────
function useDebounce(value, delay = 300) {
    const [debounced, setDebounced] = useState(value);

    useEffect(() => {
        const timer = setTimeout(() => setDebounced(value), delay);
        return () => clearTimeout(timer);
    }, [value, delay]);

    return debounced;
}

// ── Usage ──────────────────────────────────────────────────────────────────
function SearchBox() {
    const [query, setQuery] = useLocalStorage('lastQuery', '');
    const debouncedQuery    = useDebounce(query, 400);

    useEffect(() => {
        if (debouncedQuery) {
            console.log('Searching for:', debouncedQuery);
        }
    }, [debouncedQuery]);

    return (
        <input
            value={query}
            onChange={e => setQuery(e.target.value)}
            placeholder="Search…"
        />
    );
}