React hooks zijn functies zoals useState, useEffect en useRef die functiecomponenten state, side effects en refs geven. Deze cheat sheet behandelt elke ingebouwde hook in React 19.3 met een doel in één regel, een live voorbeeld en de meest gemaakte fout, plus 12 custom hooks om te kopiëren.
Op het moment van schrijven (oktober 2026) is de nieuwste React-release 19.3. De tabel toont elke hook uit de officiële React hooks-referentie plus de drie React 19-hooks die op eigen pagina's zijn gedocumenteerd (use, useOptimistic en de React DOM-hook useFormStatus). Hooks met React 18 werken in React 18 en 19. Hooks met React 19 bestaan niet in React 18. Elke React 18-hook hieronder heeft een live voorbeeld waarop u kunt klikken; de React 19-hooks staan er alleen als code, omdat deze pagina op React 18 draait.
| Hook | Groep | Gebruik het om | Sinds |
|---|---|---|---|
| useState | State | een waarde vast te houden die de component opnieuw rendert als die verandert | React 18 |
| useReducer | State | state met veel samenhangende updates te beheren via één reducerfunctie | React 18 |
| useContext | Context | een waarde te lezen van de dichtstbijzijnde provider erboven | React 18 |
| useRef | Ref | een veranderbare waarde of DOM-node te bewaren die geen renders veroorzaakt | React 18 |
| useImperativeHandle | Ref | te kiezen welke methoden een parent via een ref kan aanroepen | React 18 |
| useEffect | Effect | na het tekenen te synchroniseren met iets buiten React | React 18 |
| useLayoutEffect | Effect | de layout te meten voordat de browser tekent | React 18 |
| useInsertionEffect | Effect | stijlregels in te voegen voordat layout effects draaien (CSS-in-JS-bibliotheken) | React 18 |
| useEffectEvent | Effect | de nieuwste props in een effect te lezen zonder het opnieuw uit te voeren | React 19.2 |
| useMemo | Performance | een dure berekening tussen renders te cachen | React 18 |
| useCallback | Performance | een functie te cachen zodat gememoïseerde children niet opnieuw renderen | React 18 |
| useTransition | Performance | een state-update als niet-dringend te markeren en een pending-vlag te krijgen | React 18 |
| useDeferredValue | Performance | een traag deel van de UI achter snel typen te laten aanlopen | React 18 |
| useId | Overig | stabiele unieke ID's te maken voor labels en ARIA-attributen | React 18 |
| useSyncExternalStore | Overig | je te abonneren op een store die buiten React leeft | React 18 |
| useDebugValue | Overig | een custom hook te labelen in React DevTools | React 18 |
| useActionState | Overig | het resultaat en de pending-status van een formulieractie te volgen | React 19 |
| useOptimistic | Overig | het verwachte resultaat van een actie te tonen voordat die klaar is | React 19 |
| use | API | een promise of context te lezen, zelfs binnen voorwaarden | React 19 |
| useFormStatus | React DOM | de pending-status van het bovenliggende formulier te lezen | React 19 |
Hooks volgen twee regels: roep ze alleen aan op het hoogste niveau van een component of custom hook (nooit in loops, voorwaarden of geneste functies), en roep ze alleen aan vanuit React-functiecomponenten of andere hooks. use is de enige uitzondering op de eerste regel: die mag binnen if-blokken en loops worden aangeroepen. Het pakket eslint-plugin-react-hooks controleert beide regels voor u.
useStateWat het doet: Bewaart een waarde voor deze component. De setter aanroepen plant een nieuwe render met de nieuwe waarde.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount((c) => c - 1)}>-1</button>
<span>{count}</span>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</>
);
}
Veelgemaakte fout: setCount(count + 1) drie keer in één klik schrijven en +3 verwachten. Alle drie lezen dezelfde count uit deze render, dus u krijgt +1. Geef een updaterfunctie mee, setCount(c => c + 1), als de volgende waarde van de vorige afhangt. De knop "+3 (updater)" hierboven doet precies dat. Een tweede klassieker: een object of array ter plekke wijzigen (user.name = "x"; setUser(user)). React vergelijkt op referentie, ziet hetzelfde object en slaat de render over. Maak in plaats daarvan een nieuw object: setUser({ ...user, name: "x" }).
useReducerWat het doet: Verplaatst updatelogica naar één pure functie die de huidige state en een actie ontvangt en de volgende state teruggeeft. Handig als meerdere waarden samen veranderen.
type Action = { type: "add"; text: string } | { type: "toggle"; id: number };
function reducer(state: Todo[], action: Action): Todo[] {
switch (action.type) {
case "add":
return [...state, { id: Date.now(), text: action.text, done: false }];
case "toggle":
return state.map((t) => (t.id === action.id ? { ...t, done: !t.done } : t));
}
}
const [todos, dispatch] = useReducer(reducer, []);
dispatch({ type: "add", text: "Ship it" });
Veelgemaakte fout: Side effects uitvoeren in de reducer (fetchen, naar localStorage schrijven, willekeurige ID's maken uit een timer waarop u later vertrouwt). In Strict Mode tijdens development roept React reducers twee keer aan om onzuivere code bloot te leggen. Houd de reducer puur en zet side effects in event handlers of effects.
useContextWat het doet: Leest de waarde van de dichtstbijzijnde passende provider boven deze component, en rendert opnieuw als die waarde verandert.
const ThemeContext = createContext<"light" | "dark">("light");
function App() {
const [theme, setTheme] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={theme}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Badge() {
const theme = useContext(ThemeContext);
return <span className={theme}>Current theme: {theme}</span>;
}
Veelgemaakte fout: Bij elke render een nieuw object literal als providerwaarde meegeven, zoals value={{ user, setUser }}. Elke render maakt een nieuw object, dus elke consumer rendert opnieuw, ook als er niets veranderde. Verpak de waarde in useMemo, of splits waarden die zelden en vaak veranderen over twee contexts. Controleer ook dat de provider boven de component staat: useContext aanroepen in dezelfde component die de provider rendert, geeft de standaardwaarde terug.
useRefWat het doet: Geeft u een doosje (ref.current) dat renders overleeft. Het wijzigen veroorzaakt geen render. Gebruik het voor DOM-nodes, timer-ID's en andere waarden die het scherm niet toont.
const inputRef = useRef<HTMLInputElement>(null);
const clicks = useRef(0);
<input ref={inputRef} />
<button onClick={() => inputRef.current?.focus()}>Focus</button>
<button onClick={() => { clicks.current += 1; }}>Count silently</button>
Veelgemaakte fout: ref.current in de JSX tonen en verwachten dat het bijwerkt. Refs veroorzaken geen renders, dus het scherm loopt pas bij als iets anders opnieuw rendert (probeer hierboven de knop "Optellen bij ref" en daarna "Opnieuw renderen"). Als de waarde op het scherm moet verschijnen, hoort die in state. Vermijd ook het lezen of schrijven van refs tijdens het renderen, behalve voor luie eenmalige initialisatie.
useImperativeHandleWat het doet: Bepaalt wat een parent ontvangt als die een ref op uw component zet, zodat u een paar benoemde methoden blootstelt in plaats van de hele DOM-node.
type Handle = { shake: () => void; clear: () => void };
// React 18: wrap in forwardRef. In React 19 you can accept ref as a normal prop.
const FancyInput = forwardRef<Handle, Props>(function FancyInput(props, ref) {
const el = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
shake() { /* animate */ el.current?.focus(); },
clear() { if (el.current) el.current.value = ""; },
}), []);
return <input ref={el} />;
});
// parent
const ref = useRef<Handle>(null);
<FancyInput ref={ref} />
<button onClick={() => ref.current?.shake()}>Shake</button>
Veelgemaakte fout: Het gebruiken voor dingen die props kunnen uitdrukken. Als een parent ref.current.open() aanroept om een modal te tonen, is een isOpen-prop meestal eenvoudiger en beter te volgen. Bewaar imperatieve handles voor focus, scrollen, afspelen/pauzeren en animaties. Als u de dependency array (het derde argument) vergeet, wordt de handle ook bij elke render opnieuw aangemaakt.
useEffectWat het doet: Voert code uit nadat React het scherm heeft bijgewerkt, om een component synchroon te houden met iets buiten React: een timer, een abonnement, een netwerkverzoek, een widget die geen React is. Geef een cleanupfunctie terug om het ongedaan te maken.
useEffect(() => {
if (!running) return;
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id); // runs before the next effect and on unmount
}, [running]);
Veelgemaakte fout: Een effect gebruiken om iets te berekenen wat u tijdens het renderen kunt uitrekenen, zoals useEffect(() => setFullName(first + " " + last), [first, last]). Dat kost een extra render en kan een frame lang verouderde waarden tonen. Schrijf in plaats daarvan const fullName = first + " " + last. De op één na meest voorkomende bug is een ontbrekende cleanup: in Strict Mode tijdens development mount, unmount en hermount React elke component één keer, dus een effect zonder cleanup abonneert zich twee keer of start twee keer een timer. Die dubbele uitvoering is een hint, geen bug in React.
useLayoutEffectWat het doet: Dezelfde vorm als useEffect, maar draait nadat React de DOM wijzigt en voordat de browser tekent. Gebruik het om een element te meten en de layout aan te passen, zodat gebruikers nooit de verkeerde positie zien.
const boxRef = useRef<HTMLSpanElement>(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
setWidth(boxRef.current!.getBoundingClientRect().width);
}, [text]);
Veelgemaakte fout: Er standaard naar grijpen. Het blokkeert het tekenen, dus traag werk erin laat de pagina vastgelopen aanvoelen. Gebruik useEffect, tenzij u een flikkering ziet die alleen een meting vóór het tekenen oplost (tooltips, positionering van popovers, automatische afmetingen). Het doet ook niets tijdens server rendering, wat in sommige setups een waarschuwing geeft.
useInsertionEffectWat het doet: Draait vóór alle layout effects, zodat een bibliotheek <style>-regels kan invoegen voordat iets de layout leest. Het bestaat voor makers van CSS-in-JS-bibliotheken.
function useScopedRule(selector: string, rule: string) {
useInsertionEffect(() => {
const style = document.createElement("style");
style.textContent = `${selector} { ${rule} }`;
document.head.appendChild(style);
return () => style.remove();
}, [selector, rule]);
}
Veelgemaakte fout: Het gebruiken in applicatiecode, of er state vanuit proberen bij te werken. U kunt er geen updates in plannen en refs zijn nog niet gekoppeld. Als u geen stylingbibliotheek schrijft, wilt u vrijwel zeker useEffect of useLayoutEffect.
useEffectEventWat het doet: React 19.2 en later. Verpakt een functie zodat een effect die kan aanroepen en altijd de nieuwste props en state ziet, zonder die waarden als dependencies op te nemen. Gebruik het voor het "event"-deel van een effect, zoals loggen of een melding tonen met het huidige thema.
// React 19.2+ only. Not run on this page.
import { useEffect, useEffectEvent } from "react";
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification("Connected", theme); // always the latest theme
});
useEffect(() => {
const conn = createConnection(roomId);
conn.on("connected", () => onConnected());
conn.connect();
return () => conn.disconnect();
}, [roomId]); // theme is not a dependency, and onConnected must not be listed
}
Veelgemaakte fout: Het effect event in de dependency array zetten, het tijdens het renderen aanroepen of het aan andere componenten doorgeven. De documentatie van React zegt dat u effect events alleen vanuit effects (of andere effect events) moet aanroepen, en de lintregel waarschuwt als u er een als dependency opneemt. Op React 18 is de gebruikelijke vervanger het "latest ref"-patroon dat useInterval verderop op deze pagina gebruikt.
useMemoWat het doet: Cachet het resultaat van een berekening en rekent alleen opnieuw als de dependencies veranderen.
const filtered = useMemo(
() => items.filter((item) => item.includes(query)),
[items, query],
);
Veelgemaakte fout: Alles erin verpakken. Goedkoop werk zoals a + b of een korte map memoïseren voegt code en vergelijkingskosten toe zonder winst. Meet eerst (React DevTools Profiler) en memoïseer trage berekeningen, waarden die naar memo-children gaan en waarden die als effect-dependencies dienen. Een verwante valkuil: een dependency die bij elke render een nieuw object is ({ sort: "asc" }) laat de memo elke keer opnieuw rekenen.
useCallbackWat het doet: Geeft tussen renders dezelfde functie-instantie terug totdat een dependency verandert. Dat telt als de functie wordt doorgegeven aan een child verpakt in memo, of als effect-dependency wordt gebruikt.
const handleAdd = useCallback(() => setCount((c) => c + 1), []);
const Child = memo(function Child({ onClick }: { onClick: () => void }) {
return <button onClick={onClick}>Add</button>;
});
<Child onClick={handleAdd} />
Veelgemaakte fout: useCallback gebruiken terwijl het child niet gememoïseerd is. Zonder memo op het child rendert het toch mee met de parent, dus de stabiele functie verandert niets. De live demo toont beide children: alleen het child dat de gecachete functie ontvangt, slaat renders over. Verouderde closures zijn de andere valkuil: als de callback count leest maar de dependencylijst leeg is, blijft die de eerste waarde zien. Gebruik een updaterfunctie of neem de dependency op.
useTransitionWat het doet: Markeert een state-update als niet-dringend. React houdt het huidige scherm responsief terwijl het het nieuwe voorbereidt, en geeft u een isPending-vlag voor een laadindicatie.
const [isPending, startTransition] = useTransition();
function selectTab(next: string) {
startTransition(() => {
setTab(next);
});
}
Veelgemaakte fout: De eigen state van een tekstveld in een transition verpakken. Controlled inputs moeten direct bijwerken, anders voelt typen kapot aan. Houd de inputstate normaal en verplaats de trage, afgeleide update naar de transition (of gebruik useDeferredValue). In React 18 moet de functie die u aan startTransition meegeeft synchroon zijn; React 19 accepteert ook async functies.
useDeferredValueWat het doet: Geeft u een kopie van een waarde die mag achterlopen. Snelle delen van de UI gebruiken de echte waarde; een traag deel rendert met de uitgestelde waarde en loopt bij als de browser niets te doen heeft.
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
const isStale = query !== deferredQuery;
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<SlowResults query={deferredQuery} />
</div>
Veelgemaakte fout: Vergeten het trage child in memo te verpakken. Als het child toch bij elke toetsaanslag met de parent meerendert, helpt het uitstellen van de prop niet. Het is ook geen debounce: het wacht geen vaste tijd en vermindert geen netwerkverzoeken. Voor minder verzoeken gebruikt u debounce (zie useDebounce hieronder).
useIdWat het doet: Maakt een ID dat uniek is binnen de app en gelijk op de server en de client, voor htmlFor, aria-describedby en vergelijkbare attributen.
function Field({ label }: { label: string }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
Veelgemaakte fout: Het gebruiken om keys voor lijsten te maken. Keys moeten uit uw data komen (een ID uit de database), niet uit een hook. Vermijd ook Math.random() voor element-ID's in server-rendered apps: de server en de browser maken verschillende getallen en de hydration mislukt, precies het probleem dat useId oplost.
useSyncExternalStoreWat het doet: Abonneert een component op een databron die niet van React is (een browser-API, een globale store) en leest een snapshot die consistent blijft tijdens concurrent rendering.
function subscribe(callback: () => void) {
window.addEventListener("online", callback);
window.addEventListener("offline", callback);
return () => {
window.removeEventListener("online", callback);
window.removeEventListener("offline", callback);
};
}
const online = useSyncExternalStore(
subscribe,
() => navigator.onLine, // client snapshot
() => true, // server snapshot
);
Veelgemaakte fout: subscribe inline binnen de component definiëren. Een nieuwe functie bij elke render laat React zich elke keer afmelden en opnieuw abonneren. Definieer de functie buiten de component of verpak die in useCallback. De snapshotfunctie moet ook dezelfde waarde teruggeven als er niets veranderde: bij elke aanroep een nieuw object of array teruggeven veroorzaakt een oneindige renderloop.
useDebugValueWat het doet: Voegt een label toe aan een custom hook in React DevTools. Het heeft geen effect op wat gebruikers zien.
function useOnlineStatus() {
const online = useSyncExternalStore(subscribe, () => navigator.onLine);
useDebugValue(online ? "Online" : "Offline");
return online;
}
Veelgemaakte fout: Het aan elke custom hook toevoegen of er dure opmaak in doen. Gebruik het voor gedeelde hooks waarvan de state in DevTools moeilijk te lezen is, en geef een formatter als tweede argument mee als het label duur is om te maken: useDebugValue(date, d => d.toISOString()).
use, useActionState, useOptimistic, useFormStatus) worden niet door React 18 geëxporteerd. Deze pagina draait zelf op React 18, dus ze staan er als code die u naar een React 19-project kunt kopiëren, zonder live demo's. useEffectEvent, hierboven behandeld, heeft React 19.2 nodig.useActionStateWat het doet: Verpakt een formulieractie en geeft u het laatste resultaat plus een pending-vlag. De actie ontvangt de vorige state en de verstuurde FormData.
// React 19+
import { useActionState } from "react";
async function subscribe(prev: { error?: string; ok?: boolean }, formData: FormData) {
const email = String(formData.get("email") ?? "");
if (!email.includes("@")) return { error: "Enter a valid email" };
await saveEmail(email);
return { ok: true };
}
function Newsletter() {
const [state, formAction, isPending] = useActionState(subscribe, {});
return (
<form action={formAction}>
<input name="email" type="email" />
<button disabled={isPending}>{isPending ? "Saving..." : "Subscribe"}</button>
{state.error && <p role="alert">{state.error}</p>}
{state.ok && <p>Thanks, you are subscribed.</p>}
</form>
);
}
Veelgemaakte fout: Vergeten dat het eerste argument van de actie de vorige state is. Als u async function subscribe(formData) schrijft, is formData eigenlijk de oude state en geeft .get een fout. Let er ook op dat het tijdens de canary-periode van React 19 kort useFormState heette (in react-dom); de huidige documentatie gebruikt useActionState uit react.
useOptimisticWat het doet: Toont een tijdelijke, optimistische versie van de state terwijl een async actie loopt, en valt terug op de echte state als die klaar is.
// React 19+
import { useOptimistic } from "react";
function Thread({ messages, send }: Props) {
const [optimistic, addOptimistic] = useOptimistic(
messages,
(current, text: string) => [...current, { text, sending: true }],
);
async function action(formData: FormData) {
const text = String(formData.get("text"));
addOptimistic(text);
await send(text); // parent updates `messages` when this resolves
}
return (
<form action={action}>
{optimistic.map((m, i) => <p key={i}>{m.text}{m.sending && " (sending)"}</p>)}
<input name="text" />
</form>
);
}
Veelgemaakte fout: De optimistische setter buiten een actie of transition aanroepen. React verwacht die binnen een formulieractie of startTransition; elders verschijnt de optimistische waarde en verdwijnt die meteen (React waarschuwt tijdens development). En vang altijd fouten op: als send een fout geeft, toon dan een foutmelding zodat het bericht niet stilletjes verdwijnt.
useWat het doet: Leest de waarde van een promise (en suspendt tot die is opgelost) of van een context. Anders dan andere hooks kan het binnen voorwaarden en loops worden aangeroepen.
// React 19+
import { use, Suspense } from "react";
function Comments({ commentsPromise }: { commentsPromise: Promise<Comment[]> }) {
const comments = use(commentsPromise); // suspends until resolved
return comments.map((c) => <p key={c.id}>{c.text}</p>);
}
function Page({ commentsPromise }) {
return (
<Suspense fallback={<p>Loading comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
);
}
// Conditional context read, which useContext cannot do:
function Heading({ show }: { show: boolean }) {
if (!show) return null;
const theme = use(ThemeContext);
return <h1 className={theme}>Hello</h1>;
}
Veelgemaakte fout: De promise maken binnen de component die use aanroept, zoals use(fetch(url)). Elke render maakt een nieuwe promise, dus de component blijft eeuwig suspended. Maak de promise één keer (in een server component, een loader, een cache of een parent) en geef die door. use kan ook niet binnen try/catch; vang fouten op met een error boundary.
useFormStatusWat het doet: Geïmporteerd uit react-dom. Leest de status van het bovenliggende <form>: pending, data, method en action.
// React 19+
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>{pending ? "Sending..." : "Send"}</button>;
}
function ContactForm() {
return (
<form action={sendMessage}>
<textarea name="message" />
<SubmitButton />
</form>
);
}
Veelgemaakte fout: Het aanroepen in dezelfde component die het <form> rendert. Het rapporteert alleen over een formulier boven de aanroepende component, dus in de eigen component van het formulier is pending altijd false. Verplaats de knop naar een eigen component, zoals hierboven.
useEffect, useLayoutEffect, useMemo, useCallback en useImperativeHandle krijgen een lijst waarden als laatste argument. React vergelijkt elk item met de waarde uit de vorige render via Object.is. Als een item veranderde, draait het effect opnieuw (of rekent de memo opnieuw). Die ene regel verklaart elk geval:
| U schrijft | Het effect draait | Typisch gebruik |
|---|---|---|
| useEffect(fn) | na elke render | zelden; meestal een teken dat iets tijdens het renderen berekend moet worden |
| useEffect(fn, []) | één keer na de mount (twee keer in Strict Mode tijdens development), cleanup bij unmount | abonnementen en setup die geen props of state gebruiken |
| useEffect(fn, [a, b]) | na de mount, en daarna steeds als a of b verandert | ophalen op ID, synchroniseren met een prop |
Klik op de knoppen: b wijzigen laat alleen het effect zonder array opnieuw draaien, a wijzigen laat zowel dat effect als het [a]-effect opnieuw draaien, en het []-effect blijft na de mount stil.
eslint-plugin-react-hooks en laat exhaustive-deps vertellen wat er ontbreekt.{ id } of () => {} is volgens Object.is nooit gelijk aan de vorige, dus ze laten het effect bij elke render opnieuw draaien. Verplaats ze naar binnen het effect, naar buiten de component, of memoïseer ze.[user.id] is stabieler dan [user] als alleen het ID telt.setCount(c => c + 1) leest count niet, dus count hoeft niet in de lijst.useEffectEvent op React 19.2+, of op React 18 een ref die u up-to-date houdt.// Stale closure: logs 0 forever
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []); // count is missing
// Fixed: re-subscribe when count changes
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, [count]);
// Fetch with cleanup so a slow old response cannot overwrite a newer one
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then((r) => r.json())
.then(setUser)
.catch((err) => { if (err.name !== "AbortError") setError(err); });
return () => controller.abort();
}, [userId]);
Een custom hook is een functie waarvan de naam begint met use en die andere hooks aanroept. Zo kunnen meerdere componenten logica met state delen; elke component die de hook aanroept, krijgt een eigen, onafhankelijke state. De hooks hieronder zijn voor deze pagina in TypeScript geschreven, werken op React 18 en 19 en hebben geen dependencies. Elke hook eindigt met een opmerking over wanneer u die niet moet gebruiken.
useDebounceGeeft een waarde terug die pas bijwerkt nadat de invoer delay milliseconden niet meer is veranderd. Handig voor zoekvakken die een API aanroepen.
import { useEffect, useState } from "react";
export function useDebounce<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = window.setTimeout(() => setDebounced(value), delay);
return () => window.clearTimeout(id);
}, [value, delay]);
return debounced;
}
// const debouncedQuery = useDebounce(query, 400);
// useEffect(() => { search(debouncedQuery); }, [debouncedQuery]);
Wanneer niet gebruiken: Als u alleen wilt dat de UI responsief blijft tijdens het renderen van een grote lijst. Dat is een renderprobleem, en useDeferredValue lost het op zonder kunstmatige vertraging. Sla het ook over voor formuliervalidatie die direct moet aanvoelen.
useLocalStorageWerkt als useState, maar bewaart de waarde in localStorage en zet die bij het volgende bezoek terug.
import { useEffect, useState } from "react";
export function useLocalStorage<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(() => {
try {
const raw = window.localStorage.getItem(key);
return raw === null ? initial : (JSON.parse(raw) as T);
} catch {
return initial; // private mode, blocked storage or bad JSON
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
/* storage full or blocked: keep the in-memory value */
}
}, [key, value]);
return [value, setValue] as const;
}
Wanneer niet gebruiken: Voor alles wat gevoelig is (tokens, persoonsgegevens), omdat elk script op de pagina localStorage kan lezen. Ook niet zonder zorg voor server-rendered pagina's: de server heeft geen window, dus lees de opslag na de mount om een hydration mismatch te voorkomen. Het synchroniseert niet tussen tabbladen; voeg een storage-event listener toe als u dat nodig hebt.
useMediaQueryGeeft true terug zolang een CSS media query overeenkomt, en werkt bij als dat verandert.
import { useCallback, useSyncExternalStore } from "react";
export function useMediaQuery(query: string): boolean {
const subscribe = useCallback((onChange: () => void) => {
const mql = window.matchMedia(query);
mql.addEventListener("change", onChange);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return useSyncExternalStore(
subscribe,
() => window.matchMedia(query).matches,
() => false, // server snapshot
);
}
// const isDesktop = useMediaQuery("(min-width: 1024px)");
// const reduceMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
Wanneer niet gebruiken: Voor layoutwijzigingen die CSS zelf kan doen. Elementen verbergen of herschikken met Tailwind-breakpoints (md:hidden) werkt al voordat JavaScript laadt en flitst nooit. Gebruik de hook als u andere componenten moet renderen of iets helemaal niet wilt laden.
useToggleEen boolean met een stabiele toggle-functie, voor menu's, modals en uitklapsecties.
import { useCallback, useState } from "react";
export function useToggle(initial = false) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn((v) => !v), []);
return [on, toggle, setOn] as const;
}
// const [open, toggleOpen, setOpen] = useToggle();
Wanneer niet gebruiken: Als de state meer dan twee waarden heeft (gesloten, openend, open, fout). Gebruik een string union met useState of useReducer in plaats van meerdere toggles te stapelen.
usePreviousGeeft de waarde uit de vorige render terug, handig om een verandering te tonen ("gestegen van 12") of op een overgang te reageren.
import { useEffect, useRef } from "react";
export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
Wanneer niet gebruiken: Om te bepalen of u state moet bijwerken. De documentatie van React toont daarvoor een netter patroon: bewaar de vorige waarde in state en vergelijk tijdens het renderen. Deze hook leest een ref tijdens het renderen, wat vandaag werkt maar het soort code is dat React volgens de eigen richtlijnen zeldzaam wil houden.
useOnClickOutsideRoept een handler aan als de gebruiker ergens buiten een bepaald element drukt. Typisch voor het sluiten van een eigen menu of popover.
import { useEffect, useRef, type RefObject } from "react";
export function useOnClickOutside(
ref: RefObject<HTMLElement>,
handler: (event: PointerEvent) => void,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
const listener = (event: PointerEvent) => {
if (!ref.current || ref.current.contains(event.target as Node)) return;
saved.current(event);
};
document.addEventListener("pointerdown", listener);
return () => document.removeEventListener("pointerdown", listener);
}, [ref]);
}
Wanneer niet gebruiken: Als u al een headless bibliotheek (Radix, Headless UI) voor het menu of de dialoog gebruikt: die regelen klikken buiten het element, Escape en het terugzetten van de focus voor u. Een zelfgemaakt menu dat alleen op deze hook leunt, mist meestal toetsenbordondersteuning.
useIntervalEen setInterval die altijd de nieuwste callback aanroept en kan worden gepauzeerd door null als vertraging mee te geven.
import { useEffect, useRef } from "react";
export function useInterval(callback: () => void, delay: number | null) {
const saved = useRef(callback);
useEffect(() => { saved.current = callback; }, [callback]);
useEffect(() => {
if (delay === null) return;
const id = window.setInterval(() => saved.current(), delay);
return () => window.clearInterval(id);
}, [delay]);
}
// useInterval(() => setTicks((t) => t + 1), running ? 1000 : null);
Wanneer niet gebruiken: Voor animaties (gebruik CSS of requestAnimationFrame) of voor nauwkeurige klokken: intervallen lopen uit en browsers vertragen ze in achtergrondtabbladen. Bereken bij een aftelling de resterende tijd bij elke tik vanaf een vast eindtijdstip.
useWindowSizeGeeft de huidige breedte en hoogte van het venster terug en werkt bij als het formaat verandert.
import { useSyncExternalStore } from "react";
function subscribe(onChange: () => void) {
window.addEventListener("resize", onChange);
return () => window.removeEventListener("resize", onChange);
}
export function useWindowSize() {
const width = useSyncExternalStore(subscribe, () => window.innerWidth, () => 0);
const height = useSyncExternalStore(subscribe, () => window.innerHeight, () => 0);
return { width, height };
}
Wanneer niet gebruiken: Voor breakpoints: useMediaQuery rendert alleen opnieuw als de query omslaat, terwijl deze hook bij elke pixel van het verkleinen of vergroten opnieuw rendert. Gebruik voor het formaat van één element in plaats van het venster een ResizeObserver. (De twee aparte snapshots zijn bewust: een nieuw object teruggeven uit één snapshotfunctie zou eindeloos blijven loopen.)
useCopyToClipboardKopieert tekst met de Clipboard API en biedt een kortstondige copied-vlag voor feedback op de knop.
import { useCallback, useState } from "react";
export function useCopyToClipboard(resetAfter = 1500) {
const [copied, setCopied] = useState(false);
const copy = useCallback(async (text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopied(true);
window.setTimeout(() => setCopied(false), resetAfter);
return true;
} catch {
setCopied(false); // insecure context or permission denied
return false;
}
}, [resetAfter]);
return [copied, copy] as const;
}
Wanneer niet gebruiken: Op pagina's die via gewoon HTTP worden geserveerd: navigator.clipboard heeft een beveiligde context nodig (HTTPS of localhost). Toon altijd een alternatief, zoals de tekst selecteren, als het false teruggeeft.
useFetchLaadt JSON voor een URL, houdt laad- en foutstatus bij, en breekt het verzoek af als de URL verandert of de component unmount.
import { useEffect, useState } from "react";
type FetchState<T> = { data: T | null; error: Error | null; loading: boolean };
export function useFetch<T>(url: string | null): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({ data: null, error: null, loading: !!url });
useEffect(() => {
if (!url) return;
const controller = new AbortController();
setState((s) => ({ ...s, loading: true, error: null }));
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`Request failed with status ${res.status}`);
return res.json() as Promise<T>;
})
.then((data) => setState({ data, error: null, loading: false }))
.catch((error: Error) => {
if (error.name === "AbortError") return; // superseded or unmounted
setState({ data: null, error, loading: false });
});
return () => controller.abort();
}, [url]);
return state;
}
Wanneer niet gebruiken: In grotere apps. Het heeft geen cache, geen deduplicatie, geen nieuwe pogingen en geen revalidatie, dus twee componenten die dezelfde URL opvragen, doen twee verzoeken. Gebruik het dataladen van uw framework of een databibliotheek (TanStack Query, SWR) zodra u iets daarvan nodig hebt. De eigen documentatie van React geeft hetzelfde advies voor data ophalen in effects.
useEventListenerKoppelt een event listener aan het venster (of een ander element) en roept altijd de nieuwste handler aan, zonder zich bij elke render opnieuw te abonneren.
import { useEffect, useRef } from "react";
export function useEventListener<K extends keyof WindowEventMap>(
type: K,
handler: (event: WindowEventMap[K]) => void,
target: Window | HTMLElement | null = typeof window === "undefined" ? null : window,
) {
const saved = useRef(handler);
useEffect(() => { saved.current = handler; }, [handler]);
useEffect(() => {
if (!target) return;
const listener = (event: Event) => saved.current(event as WindowEventMap[K]);
target.addEventListener(type, listener);
return () => target.removeEventListener(type, listener);
}, [type, target]);
}
// useEventListener("keydown", (e) => { if (e.key === "Escape") close(); });
Wanneer niet gebruiken: Voor events die React al afhandelt op elementen die u rendert: gebruik onClick, onKeyDown en verwanten direct in de JSX. Grijp alleen naar deze hook voor window, document of elementen van derden.
useIsMountedGeeft een functie terug die vertelt of de component nog op het scherm staat, voor oudere async code die na een await state zet.
import { useCallback, useEffect, useRef } from "react";
export function useIsMounted() {
const mounted = useRef(false);
useEffect(() => {
mounted.current = true;
return () => { mounted.current = false; };
}, []);
return useCallback(() => mounted.current, []);
}
// const isMounted = useIsMounted();
// const data = await load(); if (isMounted()) setData(data);
Wanneer niet gebruiken: Als standaard. React 18 heeft de waarschuwing over state zetten op ge-unmounte componenten verwijderd, en de betere oplossing is het werk zelf annuleren (een AbortController, zoals in useFetch) of verouderde resultaten negeren met een vlag in de cleanup van het effect. Bewaar dit voor oudere code die u nog niet kunt herstructureren.
useState (of useReducer als meerdere waarden samen veranderen).useRef.useContext, of een externe store die u leest met useSyncExternalStore.useEffect; stap alleen over op useLayoutEffect voor metingen die een zichtbare flikkering veroorzaken.useMemo. Een gememoïseerd child blijft opnieuw renderen door een functieprop: useCallback.useDeferredValue of useTransition.useActionState, useFormStatus, useOptimistic.Bouwt u liever een componentbibliotheek dan losse hooks? Onze vergelijking van React UI-bibliotheken toont dezelfde zes componenten in shadcn/ui, Radix, Headless UI, daisyUI, Flowbite en MUI, en de Tailwind cheat sheet behandelt de utility classes die in de demo's op deze pagina worden gebruikt. Kant-en-klare onderdelen in React en Tailwind vindt u in de componentengalerij.
Op het moment van schrijven (oktober 2026, React 19.3) noemt de React hooks-referentie 17 hooks in React zelf: useState, useReducer, useContext, useRef, useImperativeHandle, useEffect, useLayoutEffect, useInsertionEffect, useEffectEvent, useMemo, useCallback, useTransition, useDeferredValue, useDebugValue, useId, useSyncExternalStore en useActionState. React 19 voegt daarnaast useOptimistic en de use-API toe, en React DOM voegt useFormStatus toe.
useMemo cachet het resultaat van het aanroepen van een functie, terwijl useCallback de functie zelf cachet. useCallback(fn, deps) is gelijk aan useMemo(() => fn, deps). Gebruik useMemo voor dure waarden en useCallback voor functies die u doorgeeft aan gememoïseerde children of als effect-dependencies gebruikt.
Tijdens development met Strict Mode aan mount React elke component, unmount die en mount die opnieuw, zodat u effects vindt waarin een cleanup ontbreekt. In een productiebuild gebeurt dat niet. Als twee keer draaien een bug veroorzaakt, voeg dan een cleanupfunctie toe die ongedaan maakt wat het effect startte.
Een lege array vertelt React dat het effect geen props of state leest, dus het draait één keer nadat de component is gemount en de cleanup draait als de component unmount. Leest het effect wel props of state, neem die dan op, anders blijft het de waarden uit de eerste render gebruiken.
Nee. use, useActionState, useOptimistic en useFormStatus worden niet door React 18 geëxporteerd, en useEffectEvent heeft React 19.2 of later nodig. Op React 18 krijgt u vergelijkbaar gedrag met useState plus event handlers voor formulieren, en met een ref die de nieuwste callback vasthoudt in plaats van useEffectEvent.
De naam begint met use en de functie roept minstens één andere hook aan. Dankzij die naamgeving kunnen de lintregels van React controleren of de regels van hooks worden gevolgd. Elke component die een custom hook aanroept, krijgt een eigen, aparte state; hooks delen logica, geen data.
Live: {title}
+3 (updater)
Resetten
Lees de sectie over useReducer
Voeg uw eigen item toe
Nieuw item
Nieuw to-do-item
Afgevinkte wissen
Ik lees "{theme}" uit de context
Providerwaarde wisselen
Klik op de knop om mij te focussen
Focusdoel
Veld focussen
Optellen bij ref (geen nieuwe render)
Opnieuw renderen
Refwaarde getoond bij laatste render: {n}
Typ hier
Veld in child
Parent roept shake() aan
Parent roept clear() aan
effect gedraaid: interval gestart
cleanup gedraaid: interval gewist
Pauzeren
Hervatten
Verander mijn breedte door te typen
Te meten tekst
Gemeten vóór het tekenen: {n}px breed
Randkleur
Deze regel is in de head van de pagina ingevoegd
Filter 4,000 items
Items filteren
Losstaande nieuwe render ({n})
{n} resultaten. Filter is {runs} keer gedraaid (losstaande nieuwe renders voeren het niet uit).
{label} (child {n}x gerenderd)
inline functie
Parent opnieuw renderen
Aantal: {n}
Over
Trage lijst
Contact
Bezig met wisselen (oud tabblad blijft klikbaar)...
Getoond: {tab}
Rij
Inhoud van tabblad {tab}.
Resultaten gerenderd voor: "{query}"
Typ snel: het veld blijft responsief
Zoeken
Naam
Netwerk:
Vensterbreedte:
Verander het formaat van het venster of zet uw netwerk uit om het te zien bijwerken.
Deze component roept een custom hook aan die zichzelf in React DevTools "{label}" noemt. Open het tabblad Components om het label naast de hook te zien.
Online
Offline
dependency arrays
[geen array] gedraaid (render {n})
[] één keer gedraaid bij mount
[a] gedraaid, a = {n}
Wijzig a ({n})
Wijzig b ({n})
Log wissen
Typ en stop dan een halve seconde
Debounce-invoer
Direct: "{text}" · Gedebounced: "{debounced}"
Typ en herlaad dan de pagina
Opgeslagen naam
Alleen in deze browser opgeslagen.
Alleen in deze browser opgeslagen: "{name}".
useMediaQuery: viewport is {size}, systeemthema is {theme}.
768px of breder
smaller dan 768px
Aan
Uit
Tel {n}
vorige was {value}
nog niets
Stoppen
Starten
{n} tikken
Gekopieerd
Kopieer een commando
Menu openen
Klik ergens buiten het menu om het te sluiten.
Gratis beginnen · Geen creditcard nodig