Die häufigsten Missverständnisse
useCallback und useMemo sind die am häufigsten falsch eingesetzten Hooks in React. Sie kosten immer etwas – Speicher, Laufzeit für den Vergleich, kognitive Komplexität – und helfen nur in spezifischen, messbaren Situationen. Wer sie überall einsetzt, macht seine Applikation langsamer, nicht schneller.
Inhaltsverzeichnis
- 1. Wie React rendert – das Fundament verstehen
- 2. Was useCallback wirklich macht
- 3. Was useMemo wirklich macht
- 4. Missverständnis 1: useCallback verhindert Re-Renders
- 5. Missverständnis 2: useMemo ist immer schneller
- 6. React.memo: das fehlende Puzzlestück
- 7. Wann useCallback und useMemo wirklich helfen
- 8. Profiling mit React DevTools
- 9. React Compiler: das Ende der manuellen Memoization?
- 10. Zusammenfassung
- 11. FAQ
1. Wie React rendert – das Fundament verstehen
React rendert eine Komponente, wenn sich ihr State oder ihre Props ändern. Beim Rendern einer Elternkomponente rendern standardmäßig alle Kindkomponenten ebenfalls neu – unabhängig davon, ob sich ihre Props geändert haben. Das ist beabsichtigt und in den meisten Fällen kein Problem: React's Reconciliation-Algorithmus ist sehr schnell, und ein Re-Render einer einfachen Komponente kostet wenige Mikrosekunden. Der Fehler liegt im Glauben, dass Re-Renders per se schlecht sind. Sie sind teuer nur wenn sie häufig und unnötig auf langsamen Komponenten auftreten.
Das Verständnis von referenziellerGleichheit ist der Schlüssel zu useCallback und useMemo. JavaScript vergleicht Objekte und Funktionen nach Referenz, nicht nach Inhalt. Das bedeutet: Zwei Funktionen mit identischem Code sind nicht gleich (() => {} !== () => {}), weil sie verschiedene Objekte im Heap sind. Jedes Mal, wenn eine Komponente rendert, werden alle Funktionen, die darin definiert sind, neu erstellt – als neue Objekte mit neuen Referenzen. Wenn diese Funktionen als Props an Kindkomponenten weitergegeben werden, sehen die Kindkomponenten bei jedem Eltern-Render neue Referenzen und rendern ebenfalls neu.
Dieser Mechanismus ist die Grundlage, auf der useCallback und useMemo funktionieren – und die Grundlage dafür, warum beide häufig missverstanden werden. Sie steuern referenzielle Gleichheit, um unnötige Re-Renders zu verhindern. Aber sie tun das nur unter einer wichtigen Voraussetzung: Die Kindkomponente muss tatsächlich mit referenzieller Gleichheit vergleichen, also entweder mit React.memo umhüllt sein oder die Referenz als Abhängigkeit eines anderen Hooks (useEffect, useQuery) verwenden.
2. Was useCallback wirklich macht
useCallback(fn, deps) gibt bei jedem Render die Funktion zurück, die beim letzten Render erstellt wurde – solange die Einträge im Dependency-Array referenziell gleich sind. Wenn sich eine Dependency ändert, wird die Funktion neu erstellt. Das ist im Wesentlichen ein Caching-Mechanismus für Funktionen: Die Funktion wird im Speicher gehalten und die Referenz bleibt stabil, solange sich die Dependencies nicht ändern.
Das Missverständnis liegt häufig im Gedanken, useCallback mache die Funktion selbst schneller. Das tut es nicht. Die Funktion, wenn sie aufgerufen wird, führt denselben Code aus wie ohne useCallback. Der einzige Unterschied ist die Stabilität der Referenz zwischen Renders. Dieses Halten im Speicher hat auch seinen Preis: useCallback speichert die Funktion, das Dependency-Array und vergleicht das Array bei jedem Render. Bei einer einfachen Funktion ohne Performance-Problem sind das drei Operationen für null Nutzen.
// Demonstration: when useCallback helps vs. when it doesn't
import { useCallback, useState, memo } from "react";
// ❌ useCallback WITHOUT React.memo on the child: zero benefit
// Child re-renders on every parent render regardless
function ParentWrong() {
const [count, setCount] = useState(0);
// This stabilizes the reference — but HeavyChild is not memoized
const handleClick = useCallback(() => {
console.log("clicked");
}, []); // stable, but unused for optimization
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>+{count}</button>
<HeavyChildNotMemoized onClick={handleClick} />
</div>
);
}
// ✅ useCallback WITH React.memo on the child: actual benefit
// Child only re-renders when handleClick reference changes
function ParentCorrect() {
const [count, setCount] = useState(0);
const [userId, setUserId] = useState(1);
// Stable reference: same function object between renders if userId didn't change
const fetchUser = useCallback(async () => {
const res = await fetch(`/api/users/${userId}`);
return res.json();
}, [userId]); // new reference only when userId changes
return (
<div>
<button onClick={() => setCount((c) => c + 1)}>Counter: {count}</button>
<MemoizedUserCard onFetch={fetchUser} />
</div>
);
}
// React.memo: only re-renders when props change by reference
const MemoizedUserCard = memo(function UserCard({ onFetch }: { onFetch: () => Promise<unknown> }) {
// This component renders only when onFetch reference changes
return <button onClick={onFetch}>Nutzer laden</button>;
});
3. Was useMemo wirklich macht
useMemo(fn, deps) führt die übergebene Funktion aus und speichert ihren Rückgabewert. Bei jedem Render wird das Dependency-Array verglichen. Nur wenn sich eine Dependency ändert, wird die Funktion neu ausgeführt und der neue Wert gespeichert. Ansonsten wird der gespeicherte Wert zurückgegeben. Das ist nützlich für Berechnungen, die teuer sind und von denselben Eingaben abhängen – eine gefilterte und sortierte Liste von tausend Elementen, eine formatierte Datentransformation, ein berechnetes Derivat von Serverdaten.
Das zweite Einsatzgebiet von useMemo ist die Stabilisierung von Objektreferenzen, die als Props weitergegeben werden. Wenn eine Komponente ein Konfigurationsobjekt an eine memoizierte Kindkomponente übergibt, würde dieses Objekt ohne useMemo bei jedem Render neu erstellt – neue Referenz, re-render der Kindkomponente. Mit useMemo bleibt die Referenz stabil, solange sich die Inputs nicht ändern. Wie bei useCallback gilt: ohne React.memo auf der Kindkomponente oder ohne die Referenz als Dependency in einem Hook bringt dieses Muster keinen Vorteil.
4. Missverständnis 1: useCallback verhindert Re-Renders
Das häufigste Missverständnis über useCallback: Es verhindert Re-Renders der Komponente, in der es aufgerufen wird. Das stimmt nicht. useCallback hat keinen Einfluss darauf, ob die aktuelle Komponente rendert. Sie rendert genau dann, wenn ihr State oder ihre Props sich ändern. useCallback stabilisiert nur die Referenz einer Funktion, damit Kindkomponenten möglicherweise seltener rendern.
Ein weiteres häufiges Szenario: useCallback wird auf eine Funktion angewendet, die als Event-Handler auf einem nativen Element (Button, Input) übergeben wird. Native HTML-Elemente verwenden keine referenzielle Gleichheit für Event-Handler – sie werden bei jedem Render ohnehin neu gesetzt. useCallback bringt hier null Vorteil, fügt aber die Komplexität des Dependency-Arrays und den Speicherverbrauch für die zwischengespeicherte Funktion hinzu. Diese Kategorie macht die Mehrheit der useCallback-Aufrufe in typischen React-Codebasen aus – und ist jedes Mal overengineering.
5. Missverständnis 2: useMemo ist immer schneller
Das Caching in useMemo hat einen messbaren Overhead: Bei jedem Render muss das Dependency-Array verglichen werden. Für jeden Eintrag im Array wird referenzielle Gleichheit geprüft. Das Ergebnis – entweder die gecachte Berechnung oder eine neue – wird gespeichert. Für einfache Berechnungen wie useMemo(() => a + b, [a, b]) ist der Overhead des Memoization-Mechanismus größer als der Overhead der Berechnung selbst. const sum = a + b ohne useMemo ist in diesem Fall schneller.
Der Break-Even-Punkt für useMemo liegt bei Berechnungen, die messbar teuer sind: Filtern und Sortieren von hunderten Objekten, komplexe Datentransformationen, reguläre Ausdrücke auf langen Strings, Geolocation-Berechnungen. Das sind die Fälle, in denen ein console.time()-Wrapper oder der React Profiler zeigt, dass die Berechnung tatsächlich Millisekunden kostet und häufig ausgeführt wird. Ohne diesen Nachweis ist useMemo premature optimization – mit einem negativen Vorzeichen.
// useMemo: when it helps vs. when it adds noise
import { useMemo, useState } from "react";
// ❌ useMemo on trivial computation — overhead > benefit
function PriceDisplay({ price, quantity }: { price: number; quantity: number }) {
// Addition is nanoseconds — memoization overhead is higher
const total = useMemo(() => price * quantity, [price, quantity]);
// Better: const total = price * quantity;
return <span>{total.toFixed(2)} €</span>;
}
// ✅ useMemo on genuinely expensive transformation
interface Order {
id: number;
status: "pending" | "shipped" | "delivered" | "cancelled";
total: number;
customer: string;
createdAt: string;
}
function OrderDashboard({ orders }: { orders: Order[] }) {
const [statusFilter, setStatusFilter] = useState<Order["status"] | "all">("all");
const [searchTerm, setSearchTerm] = useState("");
// Filtering 10,000+ orders: genuinely expensive, worth memoizing
const filteredOrders = useMemo(() => {
let result = orders;
if (statusFilter !== "all") {
result = result.filter((o) => o.status === statusFilter);
}
if (searchTerm.trim()) {
const lower = searchTerm.toLowerCase();
result = result.filter(
(o) =>
o.customer.toLowerCase().includes(lower) ||
String(o.id).includes(searchTerm)
);
}
return result;
}, [orders, statusFilter, searchTerm]); // recalculate only when inputs change
// Summary statistics — derived from filtered list, also expensive
const stats = useMemo(
() => ({
count: filteredOrders.length,
totalRevenue: filteredOrders.reduce((sum, o) => sum + o.total, 0),
avgOrderValue:
filteredOrders.length > 0
? filteredOrders.reduce((s, o) => s + o.total, 0) / filteredOrders.length
: 0,
}),
[filteredOrders]
);
return (
<div>
<p>{stats.count} Bestellungen · {stats.totalRevenue.toFixed(2)} € Umsatz</p>
</div>
);
}
6. React.memo: das fehlende Puzzlestück
React.memo ist der Wrapper, der useCallback und useMemo erst sinnvoll macht. Ohne React.memo auf der Kindkomponente ändert sich an deren Rendering-Verhalten nichts, egal wie viele Referenzen im Elternelement stabilisiert werden. React.memo(Component) erzeugt eine memoizierte Version der Komponente, die nur dann neu rendert, wenn sich ihre Props – verglichen nach referenzieller Gleichheit – geändert haben.
Das vollständige Muster für optimiertes Rendering ist immer eine Kombination: React.memo auf der Kindkomponente, useCallback für Funktions-Props im Elternelement, useMemo für Objekt-Props im Elternelement. Wenn eines dieser drei fehlt, bricht das Muster zusammen. React.memo allein hilft nicht, wenn die Elternkomponente bei jedem Render neue Funktions-Referenzen erstellt. useCallback allein hilft nicht, wenn die Kindkomponente nicht memoiziert ist. Der häufigste Fehler in der Praxis: Teams wrappen Funktionen mit useCallback, vergessen aber React.memo – und wundern sich, warum es keine Auswirkung hat.
7. Wann useCallback und useMemo wirklich helfen
Die vier legitimen Einsatzgebiete für useCallback in React: Erstens, als Prop an eine React.memo-Kindkomponente, die nur bei Referenzänderung neu rendern soll. Zweitens, als Dependency in einem useEffect, useQuery oder anderen Hook, bei dem eine neue Referenz einen ungewollten Seiteneffekt auslöst. Drittens, für Callbacks, die an externe APIs wie Event-Listener, Intersection Observer oder Websocket-Handler übergeben werden. Viertens, für Callbacks in Custom Hooks, die nach außen exponiert werden und deren Referenzstabilität die API-Garantie darstellt.
Die legitimen Einsatzgebiete für useMemo: Erstens, für messbar teure Berechnungen auf großen Datensätzen (Profiling vor der Optimierung!). Zweitens, um Objekt-Referenzen zu stabilisieren, die als Props an React.memo-Komponenten gehen. Drittens, für Berechnungen, deren Ergebnis als Dependency in Hooks verwendet wird. Die Faustregel: Wenn die Berechnung kein Array-Filter über mehr als 100 Elemente, keine komplexe Datenanreicherung und keine regulären Ausdrücke enthält, ist useMemo wahrscheinlich overengineering. Messen zuerst, optimieren danach.
8. Profiling mit React DevTools
Bevor useCallback oder useMemo eingesetzt wird, muss gemessen werden. Die React DevTools Profiler-Tab ist dafür das primäre Werkzeug. Der Workflow: Profiler öffnen, Recording starten, eine typische Benutzerinteraktion durchführen, Recording stoppen. Das Ergebnis zeigt jeden Render als Balken: Farbe (gelb = langsam, blau = schnell) und Dauer. Flamegraphs zeigen, welche Komponente in einer Render-Kette wie viel Zeit kostet. Die "Ranked" Ansicht sortiert Komponenten nach Render-Dauer.
Ein konkreter Profiling-Workflow für Re-Render-Probleme: In den DevTools-Einstellungen "Highlight updates when components render" aktivieren. Jede Komponente, die unnötig rendert, leuchtet kurz auf. Dann im Profiler nachvollziehen, warum sie rendert: "Rendered because" zeigt, welche Props sich verändert haben. Wenn eine Prop eine Funktion ist und sich bei jedem Eltern-Render ändert, ist das ein legitimer Kandidat für useCallback – aber nur wenn die Kindkomponente tatsächlich teuer zum Rendern ist. why-did-you-render als npm-Package ergänzt den Profiler durch automatische Console-Warnungen bei unnötigen Re-Renders.
9. React Compiler: das Ende der manuellen Memoization?
Der React Compiler (ehemals React Forget) ist ein Babel/SWC-Plugin, das automatisch useMemo und useCallback einfügt – überall dort, wo es sinnvoll ist. Das Compiler-Plugin analysiert die Komponente statisch, erkennt reine Berechnungen und stabile Referenzen und memoiziert sie, wo es einen Vorteil bringt. Das Ziel ist explizit, dass Entwickler nicht mehr manuell über Memoization nachdenken müssen. Ab React 19 ist der Compiler produktionsreif und wird von Meta in allen ihren React-Applikationen eingesetzt.
Die praktische Konsequenz für Teams: Mit aktiviertem React Compiler sind useCallback und useMemo für Performance-Optimierung weitgehend überflüssig. Der Compiler macht das besser und konsistenter als manuelle Entscheidungen. Die legitimen Einsatzgebiete bleiben bestehen: useCallback für API-Garantien in Custom Hooks, useMemo für explizit teure Berechnungen als Dokumentation der Absicht. Für neu gegründete React-19-Projekte gilt: React Compiler aktivieren, keine Memoization-Hooks hinzufügen, messen, und nur bei nachgewiesenem Problem manuell eingreifen.
| Situation | useCallback sinnvoll? | useMemo sinnvoll? | Begründung |
|---|---|---|---|
| Event-Handler auf Button | Nein | – | Natives Element, kein Referenzvergleich |
| Prop an React.memo-Kind | Ja | Ja (Objekte) | Referenzstabilität verhindert Re-Render |
| Dependency in useEffect | Ja | Ja | Verhindert Endlosschleife |
| a + b Berechnung | – | Nein | Overhead > Berechnung |
| Filter auf 10.000 Einträgen | – | Ja | Messbar teuer, Profiler bestätigt |
Mironsoft
React Performance Optimierung und Code-Qualität
React-App, die sich langsam anfühlt?
Wir analysieren React-Applikationen mit dem Profiler, identifizieren echte Performance-Probleme und optimieren gezielt – ohne overengineering durch übermäßige Memoization.
Performance-Audit
Profiler-Analyse zur Identifikation teurer Renders und unnötiger Re-Render-Kaskaden
Code Review
Überprüfung bestehender useCallback/useMemo-Nutzung auf tatsächlichen Nutzen und Bereinigung
React Compiler
Migration auf React 19 mit React Compiler für automatische Memoization ohne manuellen Overhead
10. Zusammenfassung
useCallback und useMemo sind keine generellen Performance-Werkzeuge – sie sind spezifische Lösungen für spezifische Probleme. useCallback stabilisiert Funktionsreferenzen, damit memoizierte Kindkomponenten seltener rendern oder Hooks mit Funktions-Dependencies keine Endlosschleifen erzeugen. useMemo speichert teure Berechnungsergebnisse oder stabilisiert Objekt-Referenzen. Beide kosten immer Speicher und Vergleichs-Overhead. Beide helfen nur, wenn React.memo auf der Kindkomponente oder ein Hook, der die Referenz als Dependency verwendet, vorhanden ist.
Die richtige Reihenfolge ist immer: messen, verstehen, optimieren. Der React DevTools Profiler zeigt, welche Renders tatsächlich teuer sind. why-did-you-render zeigt, welche Re-Renders unnötig sind. Erst nach dieser Diagnose ist klar, ob useCallback, useMemo, React.memo oder eine strukturelle Änderung das richtige Werkzeug ist. Mit aktiviertem React Compiler übernimmt der Compiler die meisten dieser Entscheidungen – manuell hinzugefügte Memoization wird dann oft zur Doppelarbeit oder sogar zum Hindernis für den Compiler.
useCallback und useMemo — Das Wichtigste auf einen Blick
useCallback hilft wenn...
Funktion als Prop an React.memo-Kindkomponente oder als Dependency in Hook. Nicht bei Event-Handlern auf nativen Elementen.
useMemo hilft wenn...
Messbar teure Berechnung (Profiler!) oder Objekt-Referenz-Stabilisierung für React.memo-Kind. Nicht für triviale Ausdrücke.
React.memo ist der Schlüssel
Ohne React.memo auf der Kindkomponente bringen useCallback und useMemo für Props keine Wirkung. Alle drei zusammen oder gar nicht.
React Compiler
In React 19 übernimmt der Compiler automatische Memoization. Messen zuerst – manuell nur eingreifen wo Profiler ein echtes Problem zeigt.