Skip to content
React core 3 min read

Hooks

Hooks are functions that let function components tap into React’s features—state, lifecycle, context, and more. Introduced in React 16.8, they replaced class components entirely. This page is a working reference for the core Hooks you’ll reach for daily.

The Rules of Hooks

Two rules make Hooks reliable, and they’re enforced by the eslint-plugin-react-hooks linter:

  1. Only call Hooks at the top level. Never inside loops, conditions, or nested functions. React tracks Hooks by call order, so that order must be identical on every render.
  2. Only call Hooks from React functions. Call them in components or in custom Hooks (functions named use…)—never in regular helpers.
// ❌ conditional Hook — breaks call order
if (loggedIn) { const [name, setName] = useState(""); }

// ✅ unconditional, top level
const [name, setName] = useState("");

useState — local state

Stores a reactive value; updating it re-renders the component. (Covered in depth in State & Events.)

const [open, setOpen] = useState(false);

Use it for any UI value that changes over time: toggles, inputs, counters.

useEffect — synchronize with the outside world

Runs side effects after render—subscriptions, timers, fetching, manual DOM work. The dependency array controls when it re-runs; the returned function cleans up.

useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id); // cleanup
}, []); // [] = run once on mount

Use it to connect to things React doesn’t manage. (Full treatment in the next chapter.)

useRef — a mutable box that survives renders

Holds a mutable .current value that persists across renders without triggering one. Two main uses: referencing a DOM node, and storing instance-like values.

function TextInput() {
  const inputRef = useRef(null);
  return (
    <>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
    </>
  );
}

Use it for DOM access, timer IDs, or any value you want to remember but not render from.

useMemo — cache an expensive computation

Memoizes a value, recomputing only when its dependencies change.

const sorted = useMemo(
  () => items.slice().sort(compareFn),
  [items]
);

Use it when a calculation is genuinely expensive, or to keep an object/array reference stable across renders.

useCallback — cache a function

Memoizes a function, returning the same reference until dependencies change. It’s useMemo specialized for functions.

const handleSelect = useCallback((id) => {
  setSelected(id);
}, []);

Use it when passing callbacks to memoized children (React.memo) so they don’t re-render needlessly.

useContext — read shared data without prop drilling

Reads the nearest value of a Context, letting deeply nested components consume data without threading props through every level.

const ThemeContext = createContext("light");

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div className={theme}>…</div>;
}

Use it for app-wide concerns: theme, current user, locale.

useReducer — structured state transitions

An alternative to useState for complex state with multiple sub-values or interdependent transitions. You dispatch actions to a reducer that returns the next state.

function reducer(state, action) {
  switch (action.type) {
    case "increment": return { count: state.count + 1 };
    case "reset":     return { count: 0 };
    default:          throw new Error("unknown action");
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0 });
// dispatch({ type: "increment" })

Use it when update logic is complex or the next state depends on the previous in non-trivial ways.

Comparison table

HookReturnsRe-renders on change?When to use
useState[value, setter]YesSimple reactive state
useReducer[state, dispatch]YesComplex / multi-step state logic
useEffectundefinedNoSide effects, subscriptions, fetching
useRef{ current }NoDOM refs, mutable values across renders
useMemomemoized valueNo (caches)Expensive computations, stable references
useCallbackmemoized functionNo (caches)Stable callbacks for memoized children
useContextcontext valueYesAvoid prop drilling for shared data

Best Practices

  • Obey the Rules of Hooks and keep the lint plugin on—it catches dependency and ordering bugs.
  • Reach for useState first; only adopt useReducer when state logic genuinely grows complex.
  • Don’t reach for useMemo/useCallback reflexively—measure first. Premature memoization adds noise without benefit.
  • Extract repeated Hook logic into custom Hooks (useLocalStorage, useDebounce) to keep components clean.
  • Keep effect dependency arrays honest—list everything the effect reads.
Last updated June 1, 2026
Was this helpful?