Hook-uri custom
Logică reutilizabilă, regulile hook-urilor, useSyncExternalStore.
Actualizat
Ce este un hook
Un hook e o funcție specială React (numele începe cu use) care îi dă unei componente acces la funcționalități React: memorie (useState), efecte (useEffect), context (useContext) etc.
Un hook custom e o funcție scrisă de tine care folosește alte hook-uri — așa extragi logică cu state și o refolosești în mai multe componente.
function useToggle(initial = false) {
const [on, setOn] = useState(initial)
const toggle = useCallback(() => setOn(v => !v), [])
return [on, toggle] as const
}
const [menuOpen, toggleMenu] = useToggle()
const [darkMode, toggleDark] = useToggle(true) // state complet separatImportant: un hook custom partajează logica, nu state-ul. Fiecare componentă care îl apelează are propria copie.
Regulile hook-urilor
- Apelezi hook-uri doar la nivelul de sus al unei componente sau al unui alt hook — nu în
if, bucle sau callback-uri. - Doar din componente React sau din alte hook-uri — nu din funcții obișnuite.
De ce: React identifică hook-urile după ordinea apelurilor la fiecare randare. Un if care sare peste un hook amestecă ordinea și state-ul ajunge la hook-ul greșit.
Hook-urile React — pe categorii
| Categorie | Hook-uri | Pentru |
|---|---|---|
| state | useState, useReducer |
memoria componentei |
| context | useContext, use(Context) |
date partajate fără props |
| efecte | useEffect, useLayoutEffect |
sincronizare cu exteriorul |
| referințe | useRef |
DOM și valori fără re-randare |
| performanță | useMemo, useCallback |
memoizare (React Compiler le face automat) |
| surse externe | useSyncExternalStore |
abonare la store-uri, media queries, localStorage |
| acțiuni (React 19) | useActionState, useOptimistic, useFormStatus, useTransition |
formulare, mutații, UI optimist |
| date async | use(promise) |
citești un Promise cu Suspense |
Exemple care merită în orice proiect
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
}
function useMediaQuery(query: string) {
return useSyncExternalStore(
cb => {
const m = matchMedia(query)
m.addEventListener('change', cb)
return () => m.removeEventListener('change', cb)
},
() => matchMedia(query).matches,
() => false, // valoarea pe server
)
}useSyncExternalStore e modul corect de a te abona la o sursă externă — exact cum detectează editorul de cod din aplicația asta tema light/dark.
Când extragi un hook custom
| Semn | Exemplu |
|---|---|
aceeași combinație useState + useEffect în 2+ componente |
debounce, fetch, media query |
| o componentă are multă logică și puțin JSX | useCheckout() scoate logica, componenta rămâne doar UI |
| vrei să testezi logica separat | hook-ul se testează cu renderHook |
Nu extrage hook-uri „pentru că se poate” — un hook folosit o singură dată, cu 3 linii, doar ascunde codul.
Unde stau în FSD
Hook legat de o entitate → entities/<x>/model/use-x.ts. Hook generic (debounce, media query) → shared/lib/.
Pe scurt
- Hook custom = funcție
use...care compune alte hook-uri; partajează logica, nu state-ul. - Doar la nivelul de sus, mereu în aceeași ordine.
useSyncExternalStorepentru surse externe; extrage hook-uri când logica se repetă.