Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як створити власний хук?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Кастомний хук** створюється як звичайна функція з іменем, що починається на `use`, всередині якої можна викликати інші хуки (`useState`, `useEffect` тощо) і яка повертає зрозумілий API для споживача. **Ключове:** дотримуйся правил хуків (виклик тільки на верхньому рівні React-функцій), правильно вказуй залежності в `useEffect`/`useMemo`/`useCallback` і стабілізуй функції та обчислення, які хук віддає назовні.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Кроки створення кастомного хука 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`.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.