Skip to main content

Як створити власний хук?

Кроки створення кастомного хука

  1. Дай ім'я з use Напр.: useLocalStorage, useDebouncedValue, useEventListener.
  2. Пиши як звичайну функцію Всередині можеш викликати інші хуки (useState, useEffect, ...).
  3. Дотримуйся правил хуків Тільки верхній рівень (не в if/for), тільки всередині React-функцій.
  4. Визнач зрозумілий API Повертай те, що потрібно споживачу: значення, сеттери, статуси (loading, error), методи.
  5. Правильно вкажи залежності У useEffect/useMemo/useCallback не забувай повний список залежностей.

Шаблон 1: базовий хук стану з побічним ефектом ("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 }; }

Використання

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> </> ); }

Шаблон 2: "прикладний" - локальне сховище (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 { // ігноруємо квоту/приватний режим } }, [key, value]); return [value, setValue] as const; }

Використання

javascript
const [theme, setTheme] = useLocalStorage<'light'|'dark'>('theme', 'light');

Шаблон 3: оптимізація вводу - "дебаунс" значення

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; }

Використання (пошук без лагів)

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> </> ); }

Патерни та поради

  • Повертай кортеж або об'єкт. Кортеж ([value, setValue]) - якщо семантика як у useState. Об'єкт - якщо повертаєш багато полів/методів.
  • Стабілізуй функції (useCallback) і обчислення (useMemo), якщо хук віддає їх назовні і це впливає на ререндери споживача.
  • Не роби мережеві запити в useLayoutEffect - звичайний useEffect підійде; кеш/скасування - через AbortController.
  • Покривай "кутові випадки". Обробляй помилки, SSR (перевіряй наявність window), відписки в cleanup.
  • Лінтинг: підключи eslint-plugin-react-hooks і не ігноруй exhaustive-deps.
  • Тестування: для юніт-тестів використовуй @testing-library/react + renderHook з @testing-library/react-hooks/@testing-library/react.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.