React Hooks

Hooks are functions that let React function components hold state, run side effects, and tap into React features. Since their introduction in React 16.8 they've replaced class components almost entirely: useState for local state, useEffect for synchronizing with external systems, useRef for mutable values and DOM access, useMemo and useCallback for caching, and a growing set of newer hooks (use, useActionState, useOptimistic, useTransition) for async data and forms.

Hooks are simple to call and surprisingly easy to misuse. Most React bugs in modern codebases come from effects that shouldn't exist, stale closures, and dependency arrays that don't match what the code actually uses. This page covers each core hook, when to reach for it, and when not to.

TL;DR

Quick Example

Core Concepts

useState and useReducer

State updates are asynchronous and batched: setCount(count + 1) twice in one handler increments once. Use functional updates when the new value depends on the old. State is replaced, not merged, so spread objects when updating (setUser(u => ({ ...u, name }))). Choose useReducer when several values change together or transitions have rules; it moves update logic into a pure, testable function.

useEffect

An effect runs after render and synchronizes the component with something outside React:

The dependency array must list every reactive value the effect uses (props, state, and values derived from them). The ESLint rule react-hooks/exhaustive-deps enforces this. In development, Strict Mode mounts, unmounts, and remounts components to surface missing cleanups.

You Might Not Need an Effect

Many effects are unnecessary and cause extra renders and bugs:

useRef

useRef returns a stable object whose .current persists across renders without causing re-renders. Two uses: DOM access (ref={inputRef}) and instance variables (timer IDs, previous values, latest callbacks). Don't read or write ref.current during render for values that affect output; that's what state is for. In React 19, ref is a regular prop on function components, so forwardRef is no longer needed.

useMemo and useCallback

useMemo(fn, deps) caches a computed value; useCallback(fn, deps) caches a function identity. They help when a computation is genuinely expensive, or when a stable identity matters (props to memo components, dependencies of other hooks). With the React Compiler enabled, React memoizes automatically, and manual use becomes the exception. See React performance.

useContext

Reads the nearest value of a context provider, which avoids prop drilling for theme, locale, auth, and similar cross-cutting data. See React Context.

React 19 Hooks

Custom Hooks

Extract reusable stateful logic into functions whose names start with use:

Each component calling a custom hook gets its own independent state; hooks share logic, not state. To share state, lift it up or use context or a store. For subscribing to external stores, prefer useSyncExternalStore, which handles concurrent rendering correctly.

The Rules of Hooks

  1. Only call hooks at the top level of a component or custom hook: not in loops, conditions, nested functions, or after early returns. React identifies hooks by call order.
  2. Only call hooks from React functions: components and custom hooks, not regular utilities or class components.

The eslint-plugin-react-hooks rules catch violations and dependency mistakes. Enable both rules-of-hooks and exhaustive-deps.

Best Practices

Keep State Minimal

Store the smallest set of values from which everything else can be computed. Duplicated or derived state gets out of sync; computing during render keeps a single source of truth.

Treat Effects as an Escape Hatch

Reach for useEffect only to synchronize with systems outside React. Before writing one, ask whether the logic belongs in rendering or in an event handler.

Always Clean Up

Effects that subscribe, set timers, or open connections must return a cleanup function. Missing cleanups cause memory leaks, duplicate listeners, and race conditions when dependencies change.

Handle Race Conditions in Async Effects

When an effect fetches data, a later request can resolve before an earlier one. Ignore stale results with a flag or AbortController, or better, use a data-fetching library that handles it.

Common Mistakes

Stale Closures

Effects That Chain State Updates

Lying About Dependencies

Silencing exhaustive-deps with an empty array when the effect uses props or state creates bugs that appear only when those values change. Restructure the effect (move functions inside it, use functional updates, or useEffectEvent for non-reactive logic) instead of omitting dependencies.

FAQ

Why does my effect run twice in development?

React Strict Mode intentionally mounts, unmounts, and remounts components in development to check that effects clean up properly. It doesn't happen in production. If the double run causes a bug, the effect is missing a cleanup.

When should I use useReducer instead of useState?

When state has several related fields updated together, when the next state depends on complex rules, or when you want update logic in a pure function you can unit test. For a single independent value, useState is simpler.

Do I still need useMemo and useCallback?

With the React Compiler, mostly not: it memoizes components and values automatically. Without it, use them for genuinely expensive computations and for values passed to memoized children or used as dependencies of other hooks. Don't wrap everything by default.

Can I call hooks conditionally?

No, except use. React relies on hooks being called in the same order every render. Put the condition inside the hook (useEffect(() => { if (enabled) … }, [enabled])) or split the component.

Related Topics

References