React hooks cheat sheet: elke hook met een live voorbeeld

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.

Alle ingebouwde hooks in één oogopslag

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.

Alle 20 getoond
HookGroepGebruik het omSinds
useStateStateeen waarde vast te houden die de component opnieuw rendert als die verandertReact 18
useReducerStatestate met veel samenhangende updates te beheren via één reducerfunctieReact 18
useContextContexteen waarde te lezen van de dichtstbijzijnde provider erbovenReact 18
useRefRefeen veranderbare waarde of DOM-node te bewaren die geen renders veroorzaaktReact 18
useImperativeHandleRefte kiezen welke methoden een parent via een ref kan aanroepenReact 18
useEffectEffectna het tekenen te synchroniseren met iets buiten ReactReact 18
useLayoutEffectEffectde layout te meten voordat de browser tekentReact 18
useInsertionEffectEffectstijlregels in te voegen voordat layout effects draaien (CSS-in-JS-bibliotheken)React 18
useEffectEventEffectde nieuwste props in een effect te lezen zonder het opnieuw uit te voerenReact 19.2
useMemoPerformanceeen dure berekening tussen renders te cachenReact 18
useCallbackPerformanceeen functie te cachen zodat gememoïseerde children niet opnieuw renderenReact 18
useTransitionPerformanceeen state-update als niet-dringend te markeren en een pending-vlag te krijgenReact 18
useDeferredValuePerformanceeen traag deel van de UI achter snel typen te laten aanlopenReact 18
useIdOverigstabiele unieke ID's te maken voor labels en ARIA-attributenReact 18
useSyncExternalStoreOverigje te abonneren op een store die buiten React leeftReact 18
useDebugValueOverigeen custom hook te labelen in React DevToolsReact 18
useActionStateOverighet resultaat en de pending-status van een formulieractie te volgenReact 19
useOptimisticOverighet verwachte resultaat van een actie te tonen voordat die klaar isReact 19
useAPIeen promise of context te lezen, zelfs binnen voorwaardenReact 19
useFormStatusReact DOMde pending-status van het bovenliggende formulier te lezenReact 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.

State-hooks

useState

Wat 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" }).

useReducer

Wat 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.

Context-hooks

useContext

Wat 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.

Ref-hooks

useRef

Wat 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.

useImperativeHandle

Wat 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.

Effect-hooks

useEffect

Wat 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.

useLayoutEffect

Wat 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.

useInsertionEffect

Wat 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.

useEffectEvent

Wat 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.

Performance-hooks

useMemo

Wat 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.

useCallback

Wat 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.

useTransition

Wat 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.

useDeferredValue

Wat 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).

Overige hooks

useId

Wat 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.

useSyncExternalStore

Wat 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.

useDebugValue

Wat 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()).

React 19-hooks (alleen code, niet uitvoerbaar op React 18)

Alleen React 19. De vier hooks in deze sectie (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.

useActionState

Wat 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.

useOptimistic

Wat 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.

use

Wat 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.

useFormStatus

Wat 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.

De dependency array uitgelegd

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 schrijftHet effect draaitTypisch gebruik
useEffect(fn)na elke renderzelden; 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 unmountabonnementen en setup die geen props of state gebruiken
useEffect(fn, [a, b])na de mount, en daarna steeds als a of b verandertophalen 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.

Vijf regels die de meeste dependency-bugs voorkomen

  1. Neem elke reactieve waarde op die de callback leest. Props, state en alles wat in de body van de component is gedeclareerd. Als u er een weglaat, krijgt u een verouderde closure: de callback houdt de waarde uit een oude render vast. Installeer eslint-plugin-react-hooks en laat exhaustive-deps vertellen wat er ontbreekt.
  2. Objecten en functies die tijdens het renderen worden gemaakt, zijn elke keer nieuw. { 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.
  3. Hang waar mogelijk af van primitieven. [user.id] is stabieler dan [user] als alleen het ID telt.
  4. Gebruik updaterfuncties om state-dependencies te laten vallen. setCount(c => c + 1) leest count niet, dus count hoeft niet in de lijst.
  5. Leg de linter niet het zwijgen op om een loop te "repareren". Een oneindige loop betekent meestal dat het effect state zet die ook een dependency is, of afhangt van een nieuw object. Los de oorzaak op. Voor waarden die u wilt lezen zonder erop te reageren, gebruikt u 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]);

12 custom hooks om naar uw project te kopiëren

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.

1. useDebounce

Geeft 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.

2. useLocalStorage

Werkt 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.

3. useMediaQuery

Geeft 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.

4. useToggle

Een 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.

5. usePrevious

Geeft 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.

6. useOnClickOutside

Roept 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.

7. useInterval

Een 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.

8. useWindowSize

Geeft 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.)

9. useCopyToClipboard

Kopieert 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.

10. useFetch

Laadt 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.

11. useEventListener

Koppelt 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.

12. useIsMounted

Geeft 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.

Welke hook heb ik nodig? Een snelle keuzelijst

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.

Veelgestelde vragen

Hoeveel ingebouwde hooks heeft React?

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.

Wat is het verschil tussen useMemo en useCallback?

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.

Waarom draait mijn useEffect twee keer?

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.

Wat betekent een lege dependency array in useEffect?

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.

Kan ik React 19-hooks zoals use of useActionState in React 18 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.

Wat maakt een functie een custom hook?

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

E-mail

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

Gerelateerd: Gidsen

Product

Voor wie

Functies

Bronnen

Bedrijf

Sitemap