Skip to content

Modern React 11: Custom hooks as reuse boundaries

This is part 11 of the Modern React development series.

Custom Hooks package reusable stateful behavior behind a function name that starts with use. They let components share logic without sharing markup, which keeps behavior reusable across different visual designs.

Concept

A custom Hook is a JavaScript function that calls React Hooks and follows the Rules of Hooks. It can use state, refs, context, Effects, reducers, and other custom Hooks, then return the values and functions a component needs.

Terms

  • Custom Hook: A function named with a use prefix that can call React Hooks.
  • Rules of Hooks: The constraints that Hooks are called at the top level of components or other Hooks.
  • Stateful logic: Behavior that uses React state, Effects, refs, context, reducers, or other Hooks.
  • Return contract: The values and functions a custom Hook exposes to its callers.

Mental model

Think of a custom Hook as a behavior adapter. The Hook owns how the behavior works. The component owns how the returned data is displayed.

How it is used

Use custom Hooks for reusable browser subscriptions, form field models, local storage state, feature flags, measurements, media queries, async status wrappers, and feature-specific state machines.

How to use it

  1. Extract logic only after two components need the same behavior or one component has become hard to read.
  2. Name the Hook after the behavior it provides.
  3. Keep JSX out of the Hook. Return data and callbacks instead.
  4. Keep the returned shape small and stable.
  5. Document the assumptions, such as browser-only APIs or provider requirements.

Example: Local storage state

import { useEffect, useState } from "react";
export function useStoredString(key: string, initialValue: string) {
const [value, setValue] = useState(() => {
return window.localStorage.getItem(key) ?? initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, value);
}, [key, value]);
return [value, setValue] as const;
}
Runtime result
export function useStoredString(key: string, initialValue: string) {
  const [value, setValue] = useState(() => {
    return window.localStorage.getItem(key) ?? initialValue;
  });
  useEffect(() => {
    window.localStorage.setItem(key, value);
  }, [key, value]);
  return [value, setValue] as const;
}

The Hook owns storage synchronization. A component can use it like state without repeating the Effect.

Example: Window size subscription

import { useEffect, useState } from "react";
export function useWindowWidth() {
const [width, setWidth] = useState(() => window.innerWidth);
useEffect(() => {
const update = () => setWidth(window.innerWidth);
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
}, []);
return width;
}
Runtime result
export function useWindowWidth() {
  const [width, setWidth] = useState(() => window.innerWidth);
  useEffect(() => {
    const update = () => setWidth(window.innerWidth);
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  }, []);
  return width;
}

The component using this Hook can focus on display, while the Hook owns the event subscription and cleanup.

Details to watch

  • Naming: The use prefix is part of how React tooling recognizes Hook rules.
  • No conditional calls: A custom Hook follows the same call order rules as built-in Hooks.
  • Browser APIs: Hooks that touch window, document, or storage need a framework-aware plan for server rendering.
  • Return shape: Return an object for many named values and a tuple for state-like pairs.

Series navigation

References