useCallback — is een React-hook die een gememoïzeerde versie van de functie retourneert, die niet verandert tussen renders zolang de afhankelijkheden niet veranderen. In tegenstelling tot de gebruikelijke declaratie van een functie binnen de component (die bij elke render een nieuwe functie maakt), stabiliseert useCallback de referentie naar de functie, waardoor onnodige re-renders van via React.memo geoptimaliseerde kindcomponenten worden voorkomen. Volgens React Documentation (2025) is useCallback alleen nuttig in combinatie met React.memo of hooks die afhankelijk zijn van een stabiele referentie.
Belangrijkste punten
useCallback — is een hook die in React 16.8 is toegevoegd en de functie memoïzeert: het retourneert dezelfde referentie zolang de afhankelijkheden niet veranderen. Zonder useCallback creëert elke functiedeclaratie binnen de component bij elke render een nieuw functieobject. Voor primitieven is dit onmerkbaar, maar bij het doorgeven van dergelijke callbacks aan via React.memo geoptimaliseerde kindcomponenten veroorzaakt elke nieuwe referentie een re-render van de kindcomponent.
Syntactisch is useCallback gelijkwaardig aan useMemo voor functies: useCallback(fn, deps) — is een afkorting voor useMemo(() => fn, deps). React slaat de gememoïzeerde functie op in de interne opslag van het fiber-knooppunt en vergelijkt de afhankelijkheden bij elke render. Als de afhankelijkheden niet zijn veranderd (Object.is voor elk element), wordt de vorige functie geretourneerd.
Volgens React Documentation — useCallback (2025) hoef je niet elke functie in useCallback te wikkelen. De hook heeft zijn eigen prijs: het aanroepen van de hook, het vergelijken van afhankelijkheden en geheugentoewijzing voor de afhankelijkheidsarray. Als de component eenvoudig is en geen diepe bomen met React.memo heeft, zal useCallback de applicatie alleen maar vertragen. Optimalisatie moet meetbaar zijn, niet intuïtief.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Stabiele referentie — dezelfde functie totdat de deps veranderen
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
Memoization in useCallback is gebaseerd op het cachen van het resultaat van de functieaanroep. React bewaart de closure die op het moment van de eerste render is gemaakt en retourneert deze bij volgende renders zolang de afhankelijkheden ongewijzigd blijven. Binnen het fiber-knooppunt creëert elke useCallback-aanroep een knooppunt in de gelinkte lijst van hooks, waar de vorige afhankelijkheden en de gememoïzeerde waarde worden opgeslagen.
Het proces van het vergelijken van afhankelijkheden gebeurt strikt via Object.is — dit is een oppervlakkige vergelijking, zonder diepgaande controle van objecten of arrays. Als er een object of array in de afhankelijkheden zit, wordt een nieuwe referentie bij elke render als een wijziging beschouwd. Daarom moeten in de afhankelijkheidsarray primitieve waarden of stabiele referenties (bijvoorbeeld uit useRef of useMemo) worden doorgegeven.
Volgens React Core Team — Optimization Guide (2024) omvat de kosten van memoization drie componenten: het toewijzen van de afhankelijkheidsarray bij elke render, het doorlopen en vergelijken van elementen via Object.is en mogelijke overhead van garbage collection bij het opnieuw creëren. Voor een component met honderden useCallback-wrappers kan dit merkbaar worden — daarom is selectiviteit in het gebruik van de hook cruciaal.
| Scenario | Zonder useCallback | Met useCallback |
|---|---|---|
| Functie aanmaken | Nieuw bij elke render | Zelfde bij stabiele deps |
| Doorgeven aan React.memo | Kind wordt re-rendered | Kind wordt niet re-rendered |
| In useEffect-array | Effect wordt opnieuw gestart | Effect is stabiel |
| Overhead | Minimaal | Afhankelijkheden vergelijken + geheugen |
Er is een wijdverbreide mythe dat useCallback automatisch de prestaties verbetert. In werkelijkheid vertraagt useCallback in isolatie (zonder React.memo) de applicatie zelfs enigszins vanwege de kosten van het vergelijken van afhankelijkheden. De hook levert alleen in drie scenario's echt voordeel op: het voorkomen van re-renders van React.memo-componenten, de stabiliteit van callbacks in useEffect en het doorgeven aan custom hooks die afhankelijk zijn van reference equality.
De regel is eenvoudig: zolang je geen prestatieprobleem hebt gevonden via React DevTools Profiler, gebruik je useCallback niet. Het React-team heeft herhaaldelijk benadrukt dat voortijdige optimalisatie de wortel van alle kwaad is. Schrijf eerst schone code zonder memoization, meet, vind de bottleneck in de profiler en voeg pas daarna useCallback toe waar het echt nodig is.
// Meetbare optimalisatie: Child is in React.memo gewikkeld
const Child = React.memo(({ onClick }) => {
console.log('Child is re-rendered');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('geklikt');
}, []);
return <Child onClick={handleClick} />;
}
Volgens Dan Abramov — Before You memo() (2024) is meer dan 90% van de useCallback-gebruiken in open-sourceprojecten overbodig. Ontwikkelaars wikkelen elke functie „voor het geval dat", zonder het effect te meten. Alternatief: als de kindcomponent zwaar is en de re-render duur — dan zijn React.memo + useCallback gerechtvaardigd. Als de kindcomponent licht is — is een re-render goedkoper dan het vergelijken van afhankelijkheden.
Het eerste scenario — React.memo. Als de kindcomponent in React.memo is gewikkeld en een callback-functie als prop accepteert, wordt de kindcomponent zonder useCallback bij elke render van de ouder opnieuw getekend, zelfs als de eigen gegevens niet zijn veranderd. useCallback stabiliseert de referentie en React.memo kan de re-render correct overslaan.
Het tweede scenario — useEffect met callback in de afhankelijkheden. Als de functie wordt doorgegeven aan de afhankelijkheidsarray van useEffect, zal elke nieuwe referentie het effect opnieuw starten. useCallback garandeert dat de referentie stabiel is en het effect alleen wordt uitgevoerd bij wijziging van de echte gegevens, niet bij elke render. Dit is vooral belangrijk voor abonnementen en verzoeken.
// useCallback voor een stabiele useEffect-afhankelijkheid
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // stabiele referentie, wordt nooit opnieuw aangemaakt
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // effect draait alleen als props.id verandert
Het belangrijkste verschil tussen useCallback en useMemo is wat elk memoïzeert. useCallback memoïzeert de functie: useCallback(fn, deps) retourneert fn (dezelfde of de vorige versie). useMemo memoïzeert het resultaat van de functieaanroep: useMemo(() => computeExpensive(a, b), [a, b]) retourneert de berekende waarde, niet de functie.
Technisch gezien is useCallback syntactische suiker bovenop useMemo: useCallback(fn, deps) is gelijkwaardig aan useMemo(() => fn, deps). Deze syntaxis bestaat alleen voor leesbaarheid — zodat de ontwikkelaar duidelijk ziet dat er precies de functie wordt gememoïzeerd, niet de waarde. Er is geen verschil in prestaties tussen useCallback en useMemo met een functie — ze genereren dezelfde code.
| Hook | Memoïzeert | Syntaxis | Gebruik |
|---|---|---|---|
| useCallback | De functie (referentie) | useCallback(fn, deps) | Callbacks voor kindcomponenten |
| useMemo | Het rekenresultaat | useMemo(() => value, deps) | Dure berekeningen, memoizen van objecten |
// Deze zijn gelijkwaardig:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
De meest voorkomende fout — het zinloos inpakken van alle functies in useCallback zonder React.memo op kindcomponenten. Als de kindcomponent niet in React.memo is gewikkeld, wordt deze toch bij elke render van de ouder re-rendered, ongeacht of de referentie van de callback verandert. useCallback zonder React.memo — kosten zonder voordeel.
// ❌ Nutteloos: geen React.memo op child
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child wordt zonder React.memo toch re-rendered
// ❌ Stale closure: ontbrekende afhankelijkheid
const handle = useCallback(() => {
console.log(count); // count is altijd 0 — stale closure!
}, []);
// ✅ Correct: neem afhankelijkheden op
const handle = useCallback(() => {
console.log(count);
}, [count]);
Het stale closure-probleem in useCallback wordt opgelost door alle gebruikte variabelen in de afhankelijkheidsarray op te nemen. eslint-plugin-react-hooks met exhaustive-deps controleert automatisch of alle variabelen uit de callback-body in de array aanwezig zijn. Als de callback setState gebruikt, die niet verandert tussen renders, kan deze veilig in de deps worden vermeld — React garandeert de stabiliteit van setState.
Veelgestelde vragen
Nee. useCallback heeft alleen zin in drie gevallen: de kindcomponent is in React.memo gewikkeld, de functie wordt gebruikt in de afhankelijkheidsarray van useEffect, of de functie wordt doorgegeven aan een custom hook die afhankelijk is van reference equality. In andere gevallen voegt useCallback overhead toe zonder voordeel. Het React-team raadt aan eerst zonder optimalisaties te schrijven en ze toe te voegen op basis van profilering.
Voor eenvoudige componenten is een nieuwe functie bij elke render iets sneller, omdat useCallback middelen verbruikt voor het vergelijken van afhankelijkheden en het toewijzen van arrays. Voor componenten met diepe React.memo-bomen wint useCallback, door de re-render van duizenden kindelementen te voorkomen. Meet en vergelijk, gok niet — gebruik React DevTools Profiler voor een objectieve beoordeling.
Ja, useCallback werkt met async-functies precies hetzelfde als met synchrone. De hook memoïzeert de functie zelf, en het resultaat (Promise) wordt elke keer bij de aanroep geretourneerd. Een asynchrone functie binnen useCallback is een veelvoorkomend patroon voor stabiele data-loading callbacks die in useEffect worden gebruikt: const fetchData = useCallback(async (id) => {...}, []).
Gebruik React DevTools Profiler — het toont welke componenten worden re-rendered en waarom. Voor programmatische controle voeg je console.log of useWhyDidYouUpdate toe — een bibliotheek die de reden van de re-render logt. De belangrijkste oorzaken: er is een prop veranderd (inclusief de callback-referentie), er is state of context veranderd. Als useCallback niet helpt, controleer dan of alle afhankelijkheden correct zijn opgegeven.
Zelfs zonder React.memo kan useCallback nuttig zijn in combinatie met useMemo voor de contextvalue. Als je een object met functies doorgeeft aan Context.Provider, wikkel het maken van het object dan in useMemo en elke functie in useCallback. Dit voorkomt de re-render van alle context-consumers bij het wijzigen van een van de functies. Maar voor het direct doorgeven van callbacks in props zonder React.memo biedt useCallback geen voordeel.
Conclusies
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook