Custom Hooks

Extract reusable stateful logic into your own use... functions.

Syntaxfunction useToggle(initial) { /* ... */ }

A custom hook is a function whose name starts with use and which calls other hooks. It lets you extract and reuse stateful logic across components without duplicating it.

Guidelines

  • Name it starting with use so the rules of hooks apply.
  • It can call useState, useEffect and other hooks.
  • Return whatever the caller needs — a value, a tuple, or an object.

Custom hooks share logic, not state — each component that calls the hook gets its own independent state.

Example

Example · javascript
import { useState, useEffect } from 'react';

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return width;
}

function Widget() {
  const width = useWindowWidth();
  return <p>Window is {width}px wide</p>;
}

When to use it

  • A team extracts their data-fetching boilerplate into a useFetch hook so every feature component gets loading, error, and data state with a single line call.
  • A form library ships a useField hook that manages value, touched, and error state for each input, eliminating repetition across every form in the codebase.
  • A realtime feature wraps WebSocket connection logic in a useWebSocket hook so any component can subscribe to a channel without knowing the connection lifecycle.

More examples

useLocalStorage custom hook

A custom hook that syncs state to localStorage, initialising lazily from storage and writing back on every update.

Example · jsx
import { useState } from 'react';

function useLocalStorage(key, initial) {
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initial;
    } catch {
      return initial;
    }
  });

  const set = v => {
    setValue(v);
    localStorage.setItem(key, JSON.stringify(v));
  };

  return [value, set];
}

// Usage
// const [theme, setTheme] = useLocalStorage('theme', 'light');

useFetch data-fetching hook

Encapsulates fetch, loading, and error state in a reusable hook so components focus only on rendering.

Example · jsx
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(r => r.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading, error };
}

// Usage
// const { data, loading } = useFetch('/api/posts');

useMediaQuery responsive hook

Wraps the MediaQueryList API in a hook so any component can reactively adapt to viewport changes.

Example · jsx
import { useState, useEffect } from 'react';

function useMediaQuery(query) {
  const [matches, setMatches] = useState(
    () => window.matchMedia(query).matches
  );

  useEffect(() => {
    const mq = window.matchMedia(query);
    const handler = e => setMatches(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

// Usage
// const isMobile = useMediaQuery('(max-width: 768px)');

Discussion

  • Be the first to comment on this lesson.