Skip to content
React rc hooks 5 min read

Hooks Overview & Rules

Hooks are plain functions that let a function component “hook into” React features that used to require class components — local state, side effects, context, refs, and more. Every hook name starts with use, and calling one inside a component subscribes that component to a slice of React’s machinery. Since they arrived in React 16.8 they have become the standard way to write React, and modern code (React 18/19) is built almost entirely from function components plus hooks. Understanding what hooks are, the two rules that govern them, and which hook does what is the foundation for everything else in this section.

What a hook actually is

A hook is just a function you call at the top of a component (or inside another hook). It returns some value — current state, a memoized callback, a ref object — and quietly registers the component with React so it can re-render when that value changes. Because hooks rely on React tracking calls in order, they only work inside a React render: a function component body, or a custom hook that is itself called from one.

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount((c) => c + 1)}>
      Clicked {count} times
    </button>
  );
}

Here useState gives the component a piece of state and a setter. React remembers the value between renders even though the function runs fresh every time. That memory is the whole point of hooks.

The two Rules of Hooks

Hooks come with exactly two rules. They are not stylistic preferences — break them and React loses track of state and crashes or behaves unpredictably.

1. Only call hooks at the top level. Never call a hook inside a loop, condition, nested function, or after an early return. Call them in the same order on every render.

2. Only call hooks from React functions. Call them from function components or from custom hooks — never from regular JavaScript functions, event handlers, or class components.

function Profile({ userId }) {
  // ❌ Wrong: conditional hook changes call order between renders
  if (userId) {
    const [name, setName] = useState("");
  }

  // ✅ Right: always call unconditionally, branch on the value instead
  const [name, setName] = useState("");
  if (!userId) return <p>Not signed in</p>;

  return <h1>{name}</h1>;
}

Why the order matters

React does not match hooks by name; it matches them by call order. On the first render it builds an internal list — first useState, then useEffect, and so on — and on every later render it walks that same list position by position to hand back the right value. If a hook is skipped one render because it sat behind an if, every hook after it shifts by one slot and gets the wrong state.

Think of hooks as a numbered list React reads top to bottom. Skipping item 2 makes item 3 grab item 2’s data. Keeping calls unconditional and in a fixed order is what keeps that list aligned.

The ESLint plugin

You should not police these rules by hand. The official eslint-plugin-react-hooks flags both violations automatically and also checks that effect dependency arrays are complete. It ships in every Vite and Create React App template, but installing it manually is trivial.

npm install --save-dev eslint-plugin-react-hooks
// eslint.config.js (flat config)
import reactHooks from "eslint-plugin-react-hooks";

export default [
  {
    plugins: { "react-hooks": reactHooks },
    rules: {
      "react-hooks/rules-of-hooks": "error",
      "react-hooks/exhaustive-deps": "warn",
    },
  },
];

The rules-of-hooks rule enforces the two rules above; exhaustive-deps warns when an effect or memo references a value it forgot to list as a dependency — the single most common source of stale-closure bugs.

Map of the built-in hooks

React ships a focused set of built-in hooks. Each solves one job; learning when to reach for which is most of the skill. The table below maps every common hook to its purpose and links to its dedicated page.

HookJob
useStateLocal component state with a setter that triggers re-renders
useReducerState managed through a reducer — better for complex transitions
useEffectRun side effects (fetches, subscriptions) after the render commits
useLayoutEffectLike useEffect but fires synchronously before the browser paints
useRefA mutable box that persists across renders without causing re-renders
useContextRead a context value provided higher in the tree
useMemoCache an expensive computed value between renders
useCallbackCache a function identity so children don’t re-render needlessly
useIdGenerate stable unique IDs for accessibility attributes
useTransitionMark state updates as non-urgent so the UI stays responsive
useDeferredValueDefer re-rendering of a value to keep input snappy
useImperativeHandleCustomize the ref a parent receives from a child
useSyncExternalStoreSubscribe safely to an external (non-React) store
useRead a promise or context conditionally during render (React 19)

Beyond these, you compose hooks into your own custom hooks — functions that bundle several built-in hooks behind a clean API, like useFetch or useLocalStorage.

Best Practices

  • Call every hook unconditionally at the very top of the component, before any early return.
  • Treat the react-hooks ESLint rules as errors in CI — they catch order and dependency bugs you cannot see by reading.
  • Name custom hooks with a use prefix so the linter recognizes and checks them.
  • Reach for the narrowest hook that fits: prefer useState until reducers genuinely simplify the logic.
  • Keep effects focused — one concern per useEffect — and always return a cleanup function when you subscribe to anything.
  • Don’t add useMemo/useCallback reflexively; apply them when profiling shows a real re-render cost.
  • Extract repeated hook combinations into custom hooks instead of copy-pasting logic between components.
Last updated June 14, 2026
Was this helpful?