Skip to main content

Custom hooks

What a custom hook is

A custom hook is a regular JS function whose name starts with use, and which uses other React hooks (useState, useEffect, etc.) inside it. It exists to extract and reuse logic (state, effects, subscriptions), without duplicating it across components.

The idea: "share behavior, not markup". The component stays clean, the UI stays separate.


How to build one (micro-examples)

1) Basic: useToggle

javascript
import { useState, useCallback } from "react"; export function useToggle(initial = false) { const [on, setOn] = useState(initial); const toggle = useCallback(() => setOn(v => !v), []); const setTrue = useCallback(() => setOn(true), []); const setFalse = useCallback(() => setOn(false), []); return { on, toggle, setTrue, setFalse }; }

2) With a side effect and cleanup: useDebounce

javascript
import { useEffect, useState } from "react"; export function useDebounce(value, delay = 300) { const [debounced, setDebounced] = useState(value); useEffect(() => { const id = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(id); // cleanup }, [value, delay]); return debounced; }

3) With local storage: useLocalStorage

javascript
import { useEffect, useState } from "react"; export function useLocalStorage(key, initial) { const [state, setState] = useState(() => { const raw = localStorage.getItem(key); return raw != null ? JSON.parse(raw) : initial; }); useEffect(() => { localStorage.setItem(key, JSON.stringify(state)); }, [key, state]); return [state, setState]; // a familiar interface, like useState }

The rules (restrictions) of hooks - must know

  1. A name starting with use The function must be called useSomething. This is not "magic", it is a convention that the linter checks.
  2. Only call hooks at the top level Not inside conditions, loops, or nested functions. The order of calls must be the same on every render.
javascript
// bad if (cond) { useEffect(...); } // good useEffect(() => { if (cond) {/*...*/} }, [cond]);
  1. Only call hooks from React functions From function components or other hooks. Not from regular functions, not from event handlers, not from classes.
  2. Custom hooks are synchronous functions The hook itself must not be async. Asynchrony belongs inside the effects.
  3. Follow exhaustive-deps The dependencies of effects/memoizations must be correct. Use eslint-plugin-react-hooks to enforce this.
  4. Do not mix responsibilities A hook is about one task (fetching, toggling, forms, syncing with storage, and so on). Small, composable hooks are better than one "giga-hook".
  5. Stable references outward If the hook returns functions/objects, memoize them (useCallback/useMemo) so consumers do not re-render unnecessarily.
  6. It does not render UI The hook returns data/methods, not JSX. Drawing is the component's job.
  7. SSR specifics Anything that depends on window/document/localStorage should be checked inside effects or conditionally (these APIs do not exist on the server).

When to build a custom hook

  • You are repeating the same useEffect + useState in several components.
  • You need to encapsulate complex logic (forms, requests, web sockets, URL↔state sync).
  • You want to provide a clean, testable API for the behavior.

Recommendations for hook APIs

  • Return a primitive, predictable contract (an array like useState, or an object with named fields).
  • Document the dependencies (what you pass into the hook, what it returns).
  • Minimize outside effects: keep anything with side effects inside useEffect with careful cleanup.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.