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
useStateholds local state;useReducerholds state with complex transitions.useEffectis for synchronizing with external systems (subscriptions, timers, non-React widgets), not for deriving data or handling events.useRefstores a mutable value that doesn't trigger re-renders, and holds DOM node references.useMemo/useCallbackcache values and functions. With the React Compiler, you rarely need them manually.- React 19 adds
use(read promises and context),useActionStateanduseOptimistic(forms and mutations). - Follow the Rules of Hooks: call them at the top level of components or custom hooks, never conditionally.
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
use(promise | context): reads a promise (suspending until it resolves) or a context. Unlike other hooks, it can be called conditionally. See Suspense.useActionState(action, initialState): wraps an async action (for example a form submission or a server action) and tracks its result and pending state.useOptimistic(state, updateFn): shows an optimistic value while an async action is in flight, reverting automatically if it fails.useFormStatus(): reads the pending state of the parent<form>from inside a child (a submit button).useTransition/useDeferredValue: mark updates as non-urgent to keep the UI responsive.
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
- 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.
- 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
- React — The library overview
- React Context — Sharing values without prop drilling
- React Performance — Memoization and rendering costs
- React Suspense —
use, loading states, and transitions - React Compiler — Automatic memoization
- State Management — When local hooks aren't enough