stackademic

The leading education platform for anyone with an interest in software development.

Custom Hooks

Extract reusable stateful logic into your own composable hooks

Overview

A custom hook is a JavaScript function whose name starts with use and that calls other hooks. It lets you extract component logic—subscriptions, data fetching, timers—into reusable functions. Custom hooks share stateful logic, not state itself: each call gets its own isolated state.

Syntax / Usage

Compose built-in hooks inside a function that returns whatever the caller needs (values, setters, or handlers).

import { useState, useEffect } from 'react'

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

  useEffect(() => {
    let active = true
    setLoading(true)

    fetch(url)
      .then((res) => res.json())
      .then((json) => active && setData(json))
      .catch((err) => active && setError(err))
      .finally(() => active && setLoading(false))

    return () => {
      active = false
    }
  }, [url])

  return { data, error, loading }
}

Examples

Persist state to localStorage with a hook that mirrors the useState signature:

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

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value))
  }, [key, value])

  return [value, setValue]
}

Track a media query for responsive rendering:

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

  useEffect(() => {
    const mql = matchMedia(query)
    const onChange = (e) => setMatches(e.matches)
    mql.addEventListener('change', onChange)
    return () => mql.removeEventListener('change', onChange)
  }, [query])

  return matches
}

Common Mistakes

  • Not prefixing the function with use, which breaks the Rules of Hooks lint checks
  • Calling hooks conditionally or inside loops within the custom hook
  • Expecting two components to share the same state—each call is independent
  • Forgetting cleanup in effects, causing memory leaks or stale updates
  • Returning unstable object/function references that trigger extra re-renders downstream

See Also

use-state use-effect react-context-patterns