Suggest an editImprove this articleRefine the answer for “How do you create a custom hook?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**A custom hook** is created as an ordinary function whose name starts with `use`, inside which you can call other hooks (`useState`, `useEffect`, etc.) and which returns a clear API for the consumer. **Key point:** follow the rules of hooks (call only at the top level of React functions), specify dependencies correctly in `useEffect`/`useMemo`/`useCallback`, and stabilize any functions and computed values the hook exposes.Shown above the full answer for quick recall.Answer (EN)Image## Steps to create a custom hook 1. **Give it a name starting with** `use` E.g.: `useLocalStorage`, `useDebouncedValue`, `useEventListener`. 2. **Write it as an ordinary function** Inside it you can call other hooks (`useState`, `useEffect`, ...). 3. **Follow the rules of hooks** Top level only (not in `if/for`), only inside React functions. 4. **Define a clear API** Return what the consumer needs: a value, setters, statuses (`loading`, `error`), methods. 5. **Specify dependencies correctly** In `useEffect`/`useMemo`/`useCallback` do not forget the full list of dependencies. --- ### Template 1: a basic state hook with a side effect ("Hello, custom hook!") **useCounter.ts** ```javascript import { useCallback, useState } from "react"; export function useCounter(initial = 0, step = 1) { const [count, setCount] = useState(initial); const inc = useCallback(() => setCount(c => c + step), [step]); const dec = useCallback(() => setCount(c => c - step), [step]); const reset = useCallback(() => setCount(initial), [initial]); return { count, inc, dec, reset }; } ``` **Usage** ```javascript function Counter() { const { count, inc, dec, reset } = useCounter(10, 2); return ( <> <p>{count}</p> <button onClick={dec}>-</button> <button onClick={inc}>+</button> <button onClick={reset}>Reset</button> </> ); } ``` --- ### Template 2: a "practical" one - local storage (persist) **useLocalStorage.ts** ```javascript import { useEffect, useState } from "react"; export function useLocalStorage<T>(key: string, initial: T) { const [value, setValue] = useState<T>(() => { try { const raw = localStorage.getItem(key); return raw != null ? JSON.parse(raw) as T : initial; } catch { return initial; } }); useEffect(() => { try { localStorage.setItem(key, JSON.stringify(value)); } catch { // ignore quota/private mode } }, [key, value]); return [value, setValue] as const; } ``` **Usage** ```javascript const [theme, setTheme] = useLocalStorage<'light'|'dark'>('theme', 'light'); ``` --- ### Template 3: input optimization - debouncing a value **useDebouncedValue.ts** ```javascript import { useEffect, useState } from "react"; export 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; } ``` **Usage (lag-free search)** ```javascript function Search({ items }: { items: string[] }) { const [q, setQ] = useState(""); const dq = useDebouncedValue(q, 350); const filtered = useMemo(() => items.filter(i => i.includes(dq)), [items, dq]); return ( <> <input value={q} onChange={e => setQ(e.target.value)} /> <ul>{filtered.map((x, i) => <li key={i}>{x}</li>)}</ul> </> ); } ``` --- ### Patterns and tips - **Return a tuple or an object.** A tuple (`[value, setValue]`) - if the semantics match `useState`. An object - if you return many fields/methods. - **Stabilize functions** (`useCallback`) and computations (`useMemo`) if the hook exposes them and that affects the consumer's re-renders. - **Do not make network requests in** `useLayoutEffect` - a regular `useEffect` is fine; caching/cancellation - through `AbortController`. - **Cover the "corners".** Handle errors, SSR (check for `window`), and unsubscribe in cleanup. - **Linting:** add `eslint-plugin-react-hooks` and do not ignore `exhaustive-deps`. - **Testing:** for unit tests use `@testing-library/react` + `renderHook` from `@testing-library/react-hooks`/`@testing-library/react`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.