Blog

Blog · Complete Guides

The Complete Guide to React Hooks in 2026

A complete guide to React Hooks — useState, useEffect, useContext, useRef, useReducer, memoization, transitions, the React 19 additions (use, Actions, useOptimistic) and custom hooks — with examples.

XCODX Team · 6 min read

React Hooks let function components use state, side effects and other React features. This complete guide covers the core hooks you’ll use daily, the performance hooks, the new React 19 additions (use, Actions, useOptimistic), and how to write your own — with the rules and common mistakes throughout. It reflects React 19, the current mainstream version.

The Rules of Hooks

Two rules make hooks work, enforced by eslint-plugin-react-hooks:

  • Call hooks only at the top level of a component or custom hook — never inside conditions, loops or nested functions (the one exception is the new use API).
  • Call hooks only from React function components or custom hooks — not plain JavaScript functions.

These rules let React reliably associate each hook call with the right piece of state across renders.

useState

useState adds local reactive state. It returns the current value and a setter; calling the setter re-renders the component. Use the updater form when the next value depends on the previous one.

const [count, setCount] = useState(0);
setCount(count + 1);           // fine
setCount(c => c + 1);          // safer when batching/depending on prev

useEffect

useEffect synchronizes your component with external systems — network, subscriptions, non-React DOM. It runs after render; the dependency array controls when it re-runs, and the returned function cleans up. A common mistake is using effects for things that aren’t external systems.

useEffect(() => {
  const sub = source.subscribe(handle);
  return () => sub.unsubscribe();   // cleanup
}, [source]);                        // re-run when `source` changes

useContext

useContext reads a Context value, avoiding prop-drilling for shared, slow-changing data like theme, locale or the current user. Every consumer re-renders when the context value changes, so keep high-frequency state out of context.

const theme = useContext(ThemeContext);

useRef

useRef gives you a mutable box that persists across renders without causing a re-render when it changes. Two main uses: referencing a DOM node, and holding a mutable value (a timer id, a previous value) that shouldn’t trigger rendering.

const inputRef = useRef(null);
// <input ref={inputRef} />
inputRef.current.focus();     // imperative DOM access

useReducer

useReducer manages state through a reducer function — ideal when the next state depends on the previous one or when updates involve several related fields or complex transitions. It’s useState’s more structured sibling.

function reducer(state, action) {
  switch (action.type) {
    case "inc": return { count: state.count + 1 };
    default: return state;
  }
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "inc" });

useMemo and useCallback

useMemo caches an expensive computed value between renders; useCallback caches a function’s identity so memoized children don’t re-render. In 2026, the React Compiler does most of this automatically — reach for these manually only when you’ve measured a need or the compiler isn’t enabled.

const sorted = useMemo(() => bigList.slice().sort(cmp), [bigList]);
const onSelect = useCallback(id => setSelected(id), []);
// With the React Compiler enabled, most of these become unnecessary.

useTransition and useDeferredValue

These keep the UI responsive during expensive updates. useTransition marks a state update as non-urgent and gives you isPending; useDeferredValue lets an expensive child lag behind a fast-changing value so typing stays smooth.

const [isPending, startTransition] = useTransition();
function onChange(e) {
  setText(e.target.value);                       // urgent
  startTransition(() => setQuery(e.target.value)); // non-urgent
}

const deferredQuery = useDeferredValue(query);

React 19 additions

React 19 introduced Actions and several new hooks for async and form workflows:

  • use — read a promise or context during render; unlike other hooks it *can* be called conditionally. Suspends while a promise is pending.
  • Actions — async functions used in transitions or as a <form action={…}>; they auto-manage pending state, errors and optimistic updates.
  • useActionState — wraps an Action, returning [state, dispatch, isPending] with the Action’s last result.
  • useOptimistic — show an immediate optimistic value while an async Action is in flight, reconciling when it completes.
  • useFormStatus (from react-dom) — read the pending status of the enclosing <form> from a child component.
// optimistic UI while an Action runs
const [optimisticLikes, addOptimistic] = useOptimistic(likes);
async function like() {
  addOptimistic(optimisticLikes + 1);
  await saveLike();     // reverts automatically if this throws
}

Custom hooks

A custom hook is a function whose name starts with use and that calls other hooks — the way you extract and reuse stateful logic. Each call gets its own isolated state; the use prefix is what lets the linter treat it as a hook.

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() =>
    JSON.parse(localStorage.getItem(key)) ?? initial);
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue];
}

Common mistakes

  • Using an Effect for derived state — compute it during render instead.
  • Missing or wrong dependency arrays — let the linter drive deps; fix by restructuring, not by lying to the array.
  • Stale closures — a callback capturing old state; fix with correct deps, the updater form, or useEffectEvent.
  • Putting event logic in Effects — handle user events in event handlers.
  • Over-memoizing — with the React Compiler, most manual useMemo/useCallback is unnecessary.

Frequently asked questions

What are React Hooks?
Hooks are functions that let React function components use features that used to require class components — local state (useState), side effects (useEffect), context (useContext), refs (useRef), and more. They must be called at the top level of a component or custom hook. Hooks replaced class components as the standard way to write React, and React 19 added new ones for async and form workflows.
When should I use useEffect?
Use useEffect to synchronize your component with an external system — fetching data, subscribing to a store or event source, or manipulating non-React DOM. Don’t use it to compute state from other state or props (derive that during render) or to handle user events (do that in the event handler). A good rule: if there’s no external system involved, you probably don’t need an Effect.
Do I still need useMemo and useCallback in React 19?
Much less often. The React Compiler (stable in 2026) automatically memoizes components, values and functions at build time, so most manual useMemo, useCallback and React.memo are no longer necessary for new code. Keep them as escape hatches when you need precise control or haven’t enabled the compiler, but don’t reach for them by default — measure first.
What is the new use() hook in React 19?
use reads a resource — a promise or a context — during render. Unlike other hooks, it can be called conditionally or inside loops. When you pass it a pending promise, the component suspends until the promise resolves (working with Suspense), which makes reading async data in render much cleaner. It’s part of React 19’s broader Actions and async-rendering improvements.

Build and run React components live in XCODX Studio — npm packages in the browser, no setup. See also React best practices, how to optimize React performance and best React libraries.