Menu

React Custom Hooks: How to Write Your Own, with Examples

A custom hook is a function whose name starts with use and that calls other hooks, so several components can reuse the same stateful logic. Build useToggle, useLocalStorage, useDebounce and useWindowWidth, and learn why each call gets its own state.

This page includes runnable editors - edit, run, and see output instantly.

A custom hook is a function whose name starts with use and that calls other hooks. You write one when two components need the same stateful logic: move the useState and useEffect calls into the function, return what the component needs, and call it from each component.

Both spoilers call useToggle, and opening one leaves the other closed. Each call runs its own useState, so each component gets its own value. A custom hook shares logic, not state.

The rules for writing one

  • The name starts with use and a capital letter: useToggle, useOnlineStatus. The lint rule for hooks uses the prefix to find hook calls and check them.
  • It calls at least one hook. A helper that calls no hooks is an ordinary function; name it formatDate, not useFormatDate, so it can be called anywhere, including inside conditions.
  • It follows the rules of hooks: call hooks at its top level, and call it only from components or other custom hooks.
  • It returns whatever is useful. An array ([on, toggle]) is handy when callers rename the values; an object ({ data, loading, error }) reads better for three or more.

useLocalStorage: state that survives a reload

useState forgets everything on reload. This hook reads the starting value from localStorage and writes every change back.

The preview runs in a sandboxed frame where the browser blocks localStorage, so here you will see the warning in the Console and the hook falls back to plain state. In a normal page the name survives a reload. The try/catch is not only for this preview: private browsing modes, blocked cookies and a full quota all make storage throw in real apps too. Passing a function to useState means storage is read once, on the first render.

useDebounce: wait until the user stops typing

A search box should not fire a request on every keystroke. useDebounce returns a copy of a value that only updates after it has stopped changing for a set delay.

Type a word quickly: Typed follows every key, Debounced catches up half a second after you stop, and the Console logs one search. Each keystroke changes value, so React runs the cleanup (clearTimeout) before starting a new timer. Change 500 to 0 and every keystroke logs its own search.

useWindowWidth: subscribe with cleanup

A hook that listens to the browser must remove its listener when the component goes away, or every mount leaves one behind.

Resize the browser window and the number follows. Click the button and the Console shows the listener being removed. Delete the return from the effect, then mount and unmount a few times: you get an added line each time and no removed, so the old listeners pile up. For browser values like this one, useSyncExternalStore is the hook React built for the job, and it is a good thing to call inside a custom hook.

Logic is shared, state is not

Two components calling the same hook get two separate copies of everything inside it. That surprises people who expect a hook to work like a global variable.

Click either button and only that counter moves. If both should show the same number, move the state into App and pass it down, or put it in context. A custom hook can wrap that shared state for convenience (useCart() reading a CartContext), but the sharing comes from the context, not from the hook.

When to write a custom hook

Extract a hook when the same group of hook calls appears in two components, or when one component's effect logic is long enough that a name would explain it better (useOnlineStatus() reads better than twenty lines of listeners). Do not wrap a single useState call in a hook just to rename it, and do not write hooks that only call useEffect to set state from props; compute that value during render instead.

Frequently Asked Questions

What is a custom hook in React?

A JavaScript function whose name starts with use and that calls one or more other hooks. It packages stateful logic (state, effects, refs) so any component can reuse it by calling the function.

Do custom hooks share state between components?

No. Every call to a custom hook runs its own useState and useEffect calls, so each component gets independent state. To share the same value between components, lift the state up or put it in context or an external store.

Why must a custom hook name start with use?

The name tells React's lint rules and other developers that the function calls hooks, so the rules of hooks apply to it: call it at the top level, never inside a condition or a loop. Without the prefix, the linter cannot check the calls inside it.

Can a custom hook return JSX?

It can return anything, but if it returns JSX it is usually better written as a component. Custom hooks typically return values and functions, as an array ([value, toggle]) or an object ({ data, error, loading }).

Can I call a custom hook inside useEffect or an event handler?

No. A custom hook is still a hook, so it follows the rules of hooks: call it at the top level of a component or of another custom hook.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED