React Hooks are functions that let you use state and other React features in function components. Beyond useState and useEffect, the advanced hooks — useContext, useReducer, useMemo, useCallback, useRef — enable complex state logic, performance optimisation, and DOM interaction. Custom hooks are the primary reuse mechanism in React.

Key Points

  • useContext: reads a Context value without prop drilling — combine with useState in a provider for app-wide state
  • useReducer: useState for complex state logic — dispatch(action) → reducer(state, action) → newState; like Redux at component level
  • useMemo(fn, deps): memoises the result of a computation — re-runs only when deps change; for expensive calculations
  • useCallback(fn, deps): memoises a function reference — prevents child re-renders caused by new function instances on each render
  • useRef: mutable ref object whose .current property persists across renders without causing re-renders — DOM refs, interval IDs, prev values
  • Custom hooks: function starting with use that calls other hooks — extract and share stateful logic between components
  • Rules of hooks: only call at the top level (no conditionals/loops), only in React function components or custom hooks
  • useId (React 18): generate stable, unique ID for accessibility labels — server-client consistent
  • useDeferredValue / useTransition (React 18): mark updates as non-urgent — keep UI responsive during expensive renders

React hooks: custom useFetch with useReducer, Context + useReducer global state, useCallback, useRef for intervals

import { useContext, useReducer, useMemo, useCallback, useRef, createContext } from 'react';

// Custom hook — fetch with loading/error state
function useFetch<T>(url: string) {
  const [state, dispatch] = useReducer(
    (s: State<T>, a: Action<T>) => {
      switch (a.type) {
        case 'loading': return { ...s, loading: true, error: null };
        case 'success': return { data: a.data, loading: false, error: null };
        case 'error':   return { ...s, loading: false, error: a.error };
        default: return s;
      }
    },
    { data: null, loading: true, error: null }
  );

  useEffect(() => {
    const ctrl = new AbortController();
    dispatch({ type: 'loading' });
    fetch(url, { signal: ctrl.signal })
      .then(r => r.json())
      .then(data => dispatch({ type: 'success', data }))
      .catch(e => { if (e.name !== 'AbortError') dispatch({ type: 'error', error: e.message }); });
    return () => ctrl.abort();
  }, [url]);

  return state;
}

// Context + useReducer — global state without Redux
const CartContext = createContext<CartContextValue | null>(null);

export function CartProvider({ children }: { children: React.ReactNode }) {
  const [cart, dispatch] = useReducer(cartReducer, { items: [], total: 0 });
  const addItem    = useCallback((item: CartItem) => dispatch({ type: 'ADD', item }), []);
  const removeItem = useCallback((id: string)     => dispatch({ type: 'REMOVE', id }), []);
  const total      = useMemo(() => cart.items.reduce((sum, i) => sum + i.price * i.qty, 0), [cart.items]);
  return (
    <CartContext.Provider value={{ cart, addItem, removeItem, total }}>
      {children}
    </CartContext.Provider>
  );
}

export const useCart = () => {
  const ctx = useContext(CartContext);
  if (!ctx) throw new Error('useCart must be used within CartProvider');
  return ctx;
};

// useRef — DOM access + interval tracking
function Timer() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef<number | null>(null);  // won't trigger re-render

  function start() {
    intervalRef.current = window.setInterval(() => setSeconds(s => s + 1), 1000);
  }
  function stop() {
    if (intervalRef.current) clearInterval(intervalRef.current);
  }
  useEffect(() => () => stop(), []);  // cleanup on unmount
  return <div>{seconds}s <button onClick={start}>Start</button></div>;
}

Real-World Example

useMemo and useCallback are often over-used. Only memoize when (1) a calculation is measurably expensive (measured with React DevTools profiler) or (2) a function/value is a dependency of another hook or passed to a memo-wrapped child. Wrapping everything in useMemo adds complexity without benefit — React's rendering is already very fast for most component sizes.