webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
React · Lesson 4 of 7

Custom hooks

Reusable logic, the rules of hooks, useSyncExternalStore.

Updated

What a hook is

A hook is a special React function (its name starts with use) that gives a component access to React features: memory (useState), effects (useEffect), context (useContext) and so on.

A custom hook is a function you write that uses other hooks — that's how you extract stateful logic and reuse it in several components.

function useToggle(initial = false) {
  const [on, setOn] = useState(initial)
  const toggle = useCallback(() => setOn(v => !v), [])
  return [on, toggle] as const
}

const [menuOpen, toggleMenu] = useToggle()
const [darkMode, toggleDark] = useToggle(true)   // completely separate state

Important: a custom hook shares logic, not state. Every component that calls it gets its own copy.

The rules of hooks

  1. Call hooks only at the top level of a component or of another hook — not inside an if, a loop or a callback.
  2. Only from React components or other hooks — not from regular functions.

Why: React identifies hooks by the order of the calls on every render. An if that skips a hook scrambles the order and the state ends up at the wrong hook.

React's hooks — by category

Category Hooks For
state useState, useReducer the component's memory
context useContext, use(Context) shared data without props
effects useEffect, useLayoutEffect synchronizing with the outside
references useRef the DOM and values without re-rendering
performance useMemo, useCallback memoization (the React Compiler does it automatically)
external sources useSyncExternalStore subscribing to stores, media queries, localStorage
actions (React 19) useActionState, useOptimistic, useFormStatus, useTransition forms, mutations, optimistic UI
async data use(promise) reading a Promise with Suspense

Examples worth having in any project

function useDebouncedValue<T>(value: T, delay = 300) {
  const [debounced, setDebounced] = useState(value)
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(id)
  }, [value, delay])
  return debounced
}

function useMediaQuery(query: string) {
  return useSyncExternalStore(
    cb => {
      const m = matchMedia(query)
      m.addEventListener('change', cb)
      return () => m.removeEventListener('change', cb)
    },
    () => matchMedia(query).matches,
    () => false,                       // the value on the server
  )
}

useSyncExternalStore is the correct way to subscribe to an external source — exactly how the code editor in this app detects the light/dark theme.

When to extract a custom hook

Sign Example
the same useState + useEffect combo in 2+ components debounce, fetch, a media query
a component has lots of logic and little JSX useCheckout() takes out the logic, the component stays pure UI
you want to test the logic separately the hook is tested with renderHook

Don't extract hooks "because you can" — a hook used once, with 3 lines, only hides the code.

Where they live in FSD

A hook tied to an entity → entities/<x>/model/use-x.ts. A generic hook (debounce, a media query) → shared/lib/.

Summary

  • A custom hook = a use... function that composes other hooks; it shares logic, not state.
  • Only at the top level, always in the same order.
  • useSyncExternalStore for external sources; extract hooks when logic repeats.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.