React Hooks

Quick reference for core and advanced React hooks with usage rules and patterns.

TL;DR

  1. 01useState and useEffect cover most common state and side-effect needs.
  2. 02Use useCallback and useMemo only when you have a proven performance issue.
  3. 03Only call hooks at the top level and inside React function components.

Tips

  1. 01Create custom hooks to share logic between components — extract state and effects into reusable hooks with names starting with "use".

Warnings

  1. 01Always follow the Rules of Hooks — calling hooks conditionally or from non-component functions breaks React's internal state management.

Core Hooks

  • useState for managing local component state.
    const [count, setCount] = useState(0);
    return <button onClick={() => setCount(count + 1)}>{count}</button>;
  • useEffect for side effects and cleanup.
    useEffect(() => {
      fetch("/api/data").then(r => r.json()).then(setData);
      return () => { /* cleanup */ };
    }, []);
  • useContext for accessing shared data from a provider.
    const { theme } = useContext(ThemeContext);
    return <div style={{ background: theme }}>Content</div>;
  • useReducer for state with multiple related actions.
    const [state, dispatch] = useReducer(reducer, { count: 0 });
    return <button onClick={() => dispatch({ type: "INCREMENT" })}>
      {state.count}
    </button>;
  • useRef for accessing DOM nodes or storing mutable values.
    const inputRef = useRef(null);
    return (
      <>
        <input ref={inputRef} />
        <button onClick={() => inputRef.current.focus()}>Focus</button>
      </>
    );

Performance Hooks

  • useCallback memoizes a function reference across renders.
    const handleClick = useCallback(() => {
      doSomething(value);
    }, [value]);
  • useMemo memoizes a computed value across renders.
    const expensiveValue = useMemo(() => {
      return computeExpensiveValue(a, b);
    }, [a, b]);
  • Use useCallback to prevent child re-renders when passing callbacks.
    const handleSubmit = useCallback((data) => {
      saveData(data);
    }, []); // stable reference — MemoizedChild won't re-render
    
    return <MemoizedChild onSubmit={handleSubmit} />;
  • Use useMemo to avoid recalculating expensive derived data.
    const sortedItems = useMemo(
      () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
      [items]
    );
  • Both hooks only optimize — they don't change behavior.
    // Add these only after profiling shows a real bottleneck
    // Premature optimization adds complexity without benefit

Ref Hooks

  • useRef creates a mutable ref that persists across renders.
    const inputRef = useRef(null);
    return (
      <>
        <input ref={inputRef} />
        <button onClick={() => inputRef.current.focus()}>Focus</button>
      </>
    );
  • useImperativeHandle exposes custom methods to parent components.
    useImperativeHandle(ref, () => ({
      focus: () => inputRef.current.focus(),
      clear: () => { inputRef.current.value = ""; }
    }));
  • Store mutable values that don't need to trigger re-renders.
    const timerRef = useRef(null);
    function start() { timerRef.current = setInterval(tick, 1000); }
    function stop() { clearInterval(timerRef.current); }
  • Track previous prop or state value for comparisons.
    const prevCountRef = useRef(count);
    useEffect(() => { prevCountRef.current = count; }, [count]);
    const prevCount = prevCountRef.current; // value from last render
  • Forward refs to child components with forwardRef.
    const Input = forwardRef((props, ref) => (
      <input ref={ref} {...props} />
    ));

Advanced Hooks

  • useLayoutEffect runs synchronously before paint — use for measurements.
    useLayoutEffect(() => {
      // Runs before browser paints — safe to read DOM measurements here
      const height = elementRef.current.offsetHeight;
      setHeight(height);
    }, []);
  • useDebugValue shows hook values in React DevTools.
    function useCustomHook(value) {
      useDebugValue(value > 10 ? "large" : "small");
      return value;
    }
  • useTransition marks an update as non-urgent to keep UI responsive.
    const [isPending, startTransition] = useTransition();
    startTransition(() => {
      setSearchResults(results); // won't block input from being typed
    });
  • useDeferredValue defers a value update without a transition.
    const deferredQuery = useDeferredValue(searchQuery);
    const results = useMemo(() => filterList(deferredQuery), [deferredQuery]);
  • useId generates a stable unique ID for accessibility attributes.
    function Input({ label }) {
      const id = useId();
      return (
        <>
          <label htmlFor={id}>{label}</label>
          <input id={id} />
        </>
      );
    }

Hook Rules

  • Only call hooks at the top level, never in loops or conditions.
    // Good
    function Component() {
      const [count, setCount] = useState(0);
      useEffect(() => { /* ... */ }, []);
    }
    
    // Bad: hook inside condition
    if (condition) {
      const [count, setCount] = useState(0); // Wrong!
    }
  • Only call hooks from React components or custom hooks.
  • Use ESLint plugin to enforce rules automatically.
    {
      "plugins": ["react-hooks"],
      "rules": {
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn"
      }
    }
  • Custom hooks must start with "use" so React enforces the rules.
    // Good: React treats useWindowWidth as a hook
    function useWindowWidth() {
      const [width, setWidth] = useState(window.innerWidth);
      useEffect(() => {
        const handler = () => setWidth(window.innerWidth);
        window.addEventListener("resize", handler);
        return () => window.removeEventListener("resize", handler);
      }, []);
      return width;
    }
  • Hooks must be called in the same order every render.
    // React tracks hooks by call order — conditional calls break this
    // Move conditions inside the hook body, not around it
    useEffect(() => {
      if (!isLoggedIn) return; // condition inside — safe
    }, [isLoggedIn]);

FAQ