Back to React
React
medium
mid

What are React hooks and how do you create custom hooks?

Hooks are functions starting with `use` that let function components opt into React features (state, effects, context, refs). A custom hook is just a function that calls other hooks — naming convention `useXxx` enables the linter to enforce the rules of hooks. Extract reusable stateful logic by composing built-in hooks into a function.

6 min read·~12 min to think through

Hooks let function components do everything class components could, plus reuse stateful logic without HOCs or render props.

Built-in hooks

HookPurpose
useStateLocal component state
useEffectSide effects after render
useContextSubscribe to context
useRefMutable container; DOM access
useMemoMemoize a computed value
useCallbackMemoize a function reference
useReducerComplex state via reducer
useTransitionDefer non-urgent updates
useDeferredValueLag a value during heavy work
useIdStable IDs across server/client
useSyncExternalStoreSubscribe to an external store

Rules of hooks

  1. Only call at the top level — never inside conditionals, loops, or nested functions.
  2. Only call from React functions — components or other hooks.

These rules let React track hooks by call order across renders. The ESLint plugin eslint-plugin-react-hooks enforces both.

Creating a custom hook

A custom hook is any function whose name starts with use and which calls other hooks.

tsx
function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);
  const inc = useCallback(() => setCount(c => c + 1), []);
  const dec = useCallback(() => setCount(c => c - 1), []);
  const reset = useCallback(() => setCount(initial), [initial]);
  return { count, inc, dec, reset };
}

Use it:

tsx
function App() {
  const { count, inc } = useCounter();
  return <button onClick={inc}>{count}</button>;
}

More examples

useDebounced

tsx
function useDebounced<T>(value: T, ms = 300) {
  const [v, setV] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setV(value), ms);
    return () => clearTimeout(id);
  }, [value, ms]);
  return v;
}

useOnlineStatus (built around an external store)

tsx
function useOnlineStatus() {
  return useSyncExternalStore(
    cb => {
      window.addEventListener('online', cb);
      window.addEventListener('offline', cb);
      return () => {
        window.removeEventListener('online', cb);
        window.removeEventListener('offline', cb);
      };
    },
    () => navigator.onLine,
    () => true, // SSR fallback
  );
}

Why hooks replaced HOCs/render-props

  • No 'wrapper hell' in the React DevTools tree.
  • Composable: hooks call hooks, naturally.
  • TypeScript inference works without ceremony.
  • Logic and UI separate cleanly: hook = logic, component = view.

When NOT to make a custom hook

If the logic isn't reused, inlining is fine. Extract when you have the second or third caller.

Follow-up questions

  • Why do hooks have to be called at the top level?
  • How does React know which state belongs to which hook call?
  • When would you use useReducer over useState?

Common mistakes

  • Calling hooks conditionally — breaks call-order tracking.
  • Naming a custom function `getCounter` instead of `useCounter` — linter can't enforce rules.
  • Building a custom hook for a single caller, before you know the abstraction is right.

Performance considerations

  • Custom hooks compile to plain function calls — no runtime overhead beyond the underlying hooks they invoke. The cost model is the same as if the logic were inlined.

Edge cases

  • Returning unstable references that consumers put in deps — infinite re-renders.
  • SSR: useSyncExternalStore needs a server snapshot to avoid hydration mismatches.
  • StrictMode dev double-mount surfaces hooks that aren't idempotent.

Real-world examples

  • Libraries built around custom hooks: TanStack Query (`useQuery`), React Hook Form (`useForm`), Zustand (`useStore`). The model has won.

Senior engineer discussion

Senior framing: a custom hook is a contract, not a refactor. Design it like a small library — parameterize inputs, return a stable shape, handle errors visibly. Then it survives reuse without surprises.

Related questions