Hooks let function components use state, lifecycle, and other React features without classes.

Rules of Hooks

  1. Only call hooks at the top level — not inside loops, conditions, or nested functions
  2. Only call hooks from React function components or custom hooks
  // WRONG
if (condition) {
    const [x, setX] = useState(0); // Violates rules
}

// CORRECT
const [x, setX] = useState(0);
if (condition) { /* use x */ }
  

Built-in Hooks Reference

Hook Purpose
useState Component state
useEffect Side effects
useContext Consume context
useRef Mutable ref, DOM access
useMemo Memoize computed values
useCallback Memoize functions
useReducer Complex state logic

useRef

Persist values across renders without causing re-renders:

  import { useRef } from 'react';

function TextInput() {
    const inputRef = useRef(null);

    const focusInput = () => {
        inputRef.current.focus();
    };

    return (
        <div>
            <input ref={inputRef} />
            <button onClick={focusInput}>Focus</button>
        </div>
    );
}

// Store previous value
function usePrevious(value) {
    const ref = useRef();
    useEffect(() => { ref.current = value; });
    return ref.current;
}
  

useMemo

Cache expensive calculations:

  import { useMemo } from 'react';

function ProductList({ products, filter }) {
    const filtered = useMemo(() => {
        return products.filter(p => p.category === filter);
    }, [products, filter]); // Recompute only when deps change

    return filtered.map(p => <ProductCard key={p.id} product={p} />);
}
  

useCallback

Cache function references (useful with memoized children):

  import { useCallback } from 'react';

function Parent() {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        setCount(c => c + 1);
    }, []); // Stable reference

    return <MemoizedChild onClick={handleClick} />;
}
  

useReducer

For complex state logic:

  import { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'increment': return { count: state.count + 1 };
        case 'decrement': return { count: state.count - 1 };
        case 'reset': return initialState;
        default: return state;
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <div>
            <p>{state.count}</p>
            <button onClick={() => dispatch({ type: 'increment' })}>+</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
        </div>
    );
}
  

Custom Hooks

Extract reusable logic:

  function useLocalStorage(key, initialValue) {
    const [value, setValue] = useState(() => {
        const saved = localStorage.getItem(key);
        return saved ? JSON.parse(saved) : initialValue;
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);

    return [value, setValue];
}

// Usage
function Settings() {
    const [theme, setTheme] = useLocalStorage('theme', 'light');
    return (
        <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
            Toggle theme: {theme}
        </button>
    );
}
  

Custom hooks must start with use. They share the same rules as built-in hooks.