React Hooks Patterns

Reusable custom hook patterns for UI state, data fetching, forms, persistence, and interaction handling.

View
StandardDetailedCompact
Export
Copy the compact sheet, download it, or print it.
Download
`D` dense toggle · `C` copy all

Custom Hook Patterns

Reusable hook composition patterns.

Basic custom hook

Extract reusable stateful logic into a function.

tsxANYreacthookscustom-hookspatterns
tsx
function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = useCallback(() => setValue(v => !v), []);
  return [value, toggle] as const;
}

Custom hooks let you share logic without changing component hierarchies.

Custom hook with options object

Prefer options objects for extensible APIs.

tsxANYreacthookscustom-hooksapi-design
tsx
function useInterval({ callback, delay, enabled = true }: {
  callback: () => void;
  delay: number;
  enabled?: boolean;
}) {
  useEffect(() => {
    if (!enabled) return;
    const id = setInterval(callback, delay);
    return () => clearInterval(id);
  }, [callback, delay, enabled]);
}

Options objects make hook signatures easier to extend without positional argument churn.

Return object from a custom hook

Use named returns for richer APIs.

tsxANYreacthookscustom-hooksapi-design
tsx
function useDisclosure(initial = false) {
  const [isOpen, setIsOpen] = useState(initial);
  return {
    isOpen,
    open: () => setIsOpen(true),
    close: () => setIsOpen(false),
    toggle: () => setIsOpen(v => !v),
  };
}

Objects are easier to read at call sites when hooks return several values or actions.

Common UI Hooks

Practical reusable UI hooks.

Track previous value

Store the previous render’s value.

tsxANYreacthooksusePreviouscustom-hooks
tsx
function usePrevious<T>(value: T) {
  const ref = useRef<T | undefined>(undefined);
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

Useful for comparisons, transitions, analytics, and animation decisions.

Persist state to localStorage

Sync component state with localStorage.

tsxANYreacthookslocalStoragecustom-hooks
tsx
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const raw = localStorage.getItem(key);
    return raw ? JSON.parse(raw) as T : initialValue;
  });
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue] as const;
}

A classic custom hook for persistence; remember browser-only constraints if server rendering is involved.

Respond to media queries

Expose a boolean that tracks a CSS media query.

tsxANYreacthooksmedia-querycustom-hooks
tsx
function useMediaQuery(query: string) {
  const [matches, setMatches] = useState(false);
  useEffect(() => {
    const media = window.matchMedia(query);
    setMatches(media.matches);
    const onChange = () => setMatches(media.matches);
    media.addEventListener('change', onChange);
    return () => media.removeEventListener('change', onChange);
  }, [query]);
  return matches;
}

Useful for responsive behavior that components need to know about in JavaScript.

Handle clicks outside an element

Close menus or popovers when a click happens outside a ref.

tsxANYreacthooksoutside-clickcustom-hooks
tsx
function useOnClickOutside<T extends HTMLElement>(ref: React.RefObject<T>, onOutside: () => void) {
  useEffect(() => {
    function handle(event: MouseEvent) {
      if (!ref.current || ref.current.contains(event.target as Node)) return;
      onOutside();
    }
    document.addEventListener('mousedown', handle);
    return () => document.removeEventListener('mousedown', handle);
  }, [ref, onOutside]);
}

Common for menus, dialogs, tooltips, and dropdowns.

Data Fetching Patterns

Hooks for loading, caching, and race-safe fetching.

Fetch with AbortController

Cancel stale requests on dependency change.

tsxANYreacthooksfetchAbortController
tsx
useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
    .then(r => r.json())
    .then(setResults)
    .catch(err => {
      if (err.name !== 'AbortError') throw err;
    });
  return () => controller.abort();
}, [query]);

Aborting requests is cleaner than race flags when the underlying API supports it.

Track loading, error, and data state

Model async UI state explicitly.

tsxANYreacthooksasyncstate
tsx
const [data, setData] = useState<User[] | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(false);

A simple foundation for robust async UIs before introducing a data library.

Expose derived async status

Return a status object from custom data hooks.

tsxANYreacthookscustom-hooksdata-fetching
tsx
return {
  data,
  error,
  loading,
  isEmpty: !loading && !error && Array.isArray(data) && data.length === 0,
};

Derived flags simplify call sites and reduce repeated UI conditionals.

Forms and Event Patterns

Form and interaction helpers built with hooks.

Controlled form state

Manage a form object with useState.

tsxANYreacthooksformsuseState
tsx
const [form, setForm] = useState({ email: '', password: '' });
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;
  setForm(prev => ({ ...prev, [name]: value }));
};

A standard controlled form pattern for smaller forms.

Complex form with useReducer

Use a reducer when form behavior becomes more complex.

tsxANYreacthooksformsuseReducer
tsx
function formReducer(state: FormState, action: FormAction): FormState {
  switch (action.type) {
    case 'change':
      return { ...state, [action.name]: action.value };
    case 'reset':
      return action.initial;
    default:
      return state;
  }
}

Reducers become easier to scale than ad hoc state updates for complex forms.

Debounced input value

Delay reactions to rapidly changing values.

tsxANYreacthooksdebouncecustom-hooks
tsx
function useDebouncedValue<T>(value: T, delay: number) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

Useful for search, auto-save, analytics, and expensive derived computations.

Recommended next

No recommendations yet.