useCallback() verstehen und anwenden
)
useCallback() memoisiert eine Funktionsreferenz zwischen Re-Renders, solange sich die Dependencies nicht ändern. Im Kern ist es useMemo(() => fn, deps) für Funktionen. Wann ist es wichtig, dass eine Funktion über mehrere Renderings dieselbe Referenz behält? Spoileralarm! Hier gilt weitgehend das gleiche wie für useMemo().
1. Das Grundproblem: Referenzielle Gleichheit
Bei JavaScript muss man zwischen Wertgleichheit und Referenzgleichheit unterscheiden. Bei primitiven Werten wie Strings, Zahlen oder Boolean ist das relativ einfach:
const a = 42;
const b = 42;
console.log(a === b); // trueBei Objekten, Arrays und Funktionen sieht es anders aus:
const a = { name: "Frank" };
const b = { name: "Frank" };
console.log(a === b); // falseObwohl die beiden Objekte denselben Inhalt haben, sind sie zwei verschiedene Objekte.
Man kann sich vorstellen:
a ───────► { name: "Frank" }
b ───────► { name: "Frank" }a und b zeigen auf unterschiedliche Objekte im Speicher.
Deshalb:
a === b // falseDerselbe Objektverweis
Wenn du dagegen ein Objekt kopierst, ohne ein neues Objekt zu erzeugen:
const a = { name: "Frank" };
const b = a;
console.log(a === b); // trueJetzt:
a ─────┐
├──► { name: "Frank" }
b ─────┘Beide Variablen referenzieren dasselbe Objekt.
Das ist referenzielle Gleichheit.
Funktionen und Arrays sind in JavaScript ebenfalls Objekte und unterliegen deshalb demselben Prinzip.
React verwendet an vielen Stellen referenzielle Vergleiche (Shallow Comparison), statt Objekte tief miteinander zu vergleichen (Deep Comparison). Das ist eine wichtige Performanceentscheidung.
Jeder Re-Render erzeugt eine neue Referenz obwohl die Werte oder die Funktionslogik gleich geblieben sind. Das kann zu einigen Problemen führen, die nachfolgend besprochen und gelöst werden. Das Lösungsprinzip ist dabei immer gleich: Wir stabilisieren die Referenz.
2. Funktionen als Props für Kind-Komponenten mit React.memo()
Für eine Funktion als Prop ohne useCallback erzeugt jede Elternkomponente bei jedem Render eine neue Funktionsreferenz – React.memo() an der Kind-Komponente bringt dann nichts, weil die Prop-Referenz sich "ändert", obwohl die Logik gleich bleibt. Die Kind-Komponente würde erneut rendern weil sich die Props geändert haben. Das ist ein klassischer Fall für useCallback. Hier ein Beispiel um dies zu verhindern:
const handleClick = useCallback(() => {
doSomething(id);
}, [id]);
<MemoizedButton onClick={handleClick} /> // memoisiert mit React.memo()3. Funktionen als Dependencies von Hooks
3.1. Als Dependency in useEffect
Wenn eine Funktion innerhalb eines useEffect verwendet wird und selbst eine Dependency ist, verhindert useCallback, dass der Effect bei jedem Render erneut ausgelöst wird. Im folgenden Beispiel werden dadurch unnötige Fetches vermieden:
const fetchData = useCallback(() => {
api.get(`/items/${id}`);
}, [id]);
useEffect(() => {
fetchData();
}, [fetchData]);3.2. Als Dependency in useMemo
Analog dazu: Wenn eine Berechnung in useMemo von einer Callback-Funktion abhängt.
function Calculator({ value }) {
const [multiplier, setMultiplier] = useState(2);
// Stabile Funktion, ändert sich nur wenn multiplier sich ändert
const multiply = useCallback(
(num) => num * multiplier,
[multiplier]
);
// Nutzt multiply als Dependency
const result = useMemo(() => {
console.log('berechne neu...');
return multiply(value);
}, [value, multiply]);
return <div>Ergebnis: {result}</div>;
}Einfacher wäre es, die Funktion außerhalb der Komponente zu halten, zum Beispiel im Verzeichnis /libs. Das ist aber nicht immer möglich, zum Beispiel wenn die Funktion Props oder State der Komponente als Argumente benötigt.
3.3. Als Dependency in Custom Hooks
Ein Custom Hook mit Funktion als Dependency:
// Custom Hook, der eine Callback-Funktion als Parameter bekommt
function useInterval(callback, delay) {
useEffect(() => {
const id = setInterval(callback, delay);
return () => clearInterval(id);
}, [callback, delay]); // callback ist Dependency
}Verwendung – callback muss stabilisiert werden:
function Timer() {
const [count, setCount] = useState(0);
const tick = useCallback(() => {
setCount(c => c + 1);
}, []); // stabil, da setCount garantiert stabil ist
useInterval(tick, 1000);
return <div>Sekunden: {count}</div>;
}Ohne useCallback bei tick würde bei jedem Render von Timer eine neue Funktionsreferenz entstehen. Das useEffect innerhalb von useInterval sähe dann bei jedem Render eine "geänderte" Dependency (callback) und würde das Interval ständig löschen und neu erstellen – im schlimmsten Fall tickt der Timer nie richtig, weil er permanent zurückgesetzt wird.
3.4. In Custom Hooks, die Funktionen zurückgeben
Hier ein sehr reduziertes Beispiel, das useCallback innerhalb eines Custom Hooks zeigt:
function useCounter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
return { count, increment };
}Verwendung:
function Counter() {
const { count, increment } = useCounter();
return (
<div>
<p>Zähler: {count}</p>
<button onClick={increment}>+</button>
</div>
);
}Wenn ein Custom Hook Funktionen exportiert, sollten diese stabil sein, damit Consumer des Hooks nicht unnötig re-rendern oder Effects triggern.
4. Zum vermeiden von Re-Render-Kaskaden bei Context
Relevant ist useCallback() auch in Bezug auf die Context – und zwar in einem sehr ähnlichen Fall wie bei useMemo und Context. Wenn der Context-Value Funktionen enthält, sorgt useCallback dafür, dass nicht alle Consumer unnötig neu rendern.
4.1. Das Problem ohne useCallback
function AppProvider({ children }) {
const [user, setUser] = useState(null);
// Neue Funktionsreferenz bei JEDEM Render von AppProvider
const login = (credentials) => {
// ... login logic
setUser(credentials);
};
const value = { user, login };
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}Jeder Consumer, der useContext(AppContext) nutzt, rendert bei jedem Render von AppProvider neu – selbst wenn user sich gar nicht geändert hat. Grund: value ist ein neues Objekt, und login ist eine neue Funktion, bei jedem einzelnen Render.
4.2. Die Lösung: useCallback + useMemo kombiniert
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const login = useCallback((credentials) => {
setUser(credentials);
}, []); // stabil, da setUser garantiert stabil ist
const value = useMemo(
() => ({ user, login }),
[user, login]
);
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}Warum beide Hooks nötig sind:
useCallbackstabilisiertloginselbstuseMemostabilisiert dasvalue-Objekt, dasloginenthält
Ohne useCallback würde login bei jedem Render neu erzeugt → useMemo sähe eine "geänderte" Dependency → value würde trotzdem neu erstellt → der ganze useMemo-Aufwand wäre umsonst.
5. Alternativen zu useCallback
useCallback ist oft nicht die einzige oder die beste Lösung. Hier die Alternativen, geordnet danach, wie oft sie in der Praxis tatsächlich sinnvoll sind:
5.1. Funktion aus der Komponente rausziehen
Wenn die Funktion keine Props/State aus der Komponente braucht, muss sie gar nicht innerhalb der Komponente definiert werden – dann ist sie automatisch stabil, ganz ohne Hook.
5.2. useState mit Updater-Funktion statt Closure über State
Oft braucht man useCallback nur, weil eine Funktion von state abhängt. Mit der Updater-Form (c => c + 1) entfällt die Dependency komplett.
// Braucht [count] als Dependency
const increment = useCallback(() => setCount(count + 1), [count]);
// Keine Dependency nötig, count wird nie "gebraucht"
const increment = useCallback(() => setCount(c => c + 1), []);5.3. useReducer statt mehrerer Callbacks
Wenn du viele zusammengehörige State-Updates + Callbacks hast, wird dispatch von React garantiert stabil geliefert – kein useCallback nötig.
function reducer(state, action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 };
case 'decrement': return { count: state.count - 1 };
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
// dispatch ist stabil, kein useCallback nötig für die "Handler"
return <button onClick={() => dispatch({ type: 'increment' })}>+</button>;
}5.4. useRef für Werte, die keine Re-Renders auslösen sollen
Wenn eine "Funktion" eigentlich nur einen aktuellen Wert referenzieren soll (z. B. bei Event-Listenern, Timern), ist useRef oft passender als useCallback mit vielen Dependencies.
function useLatestCallback(callback) {
const ref = useRef(callback);
useEffect(() => { ref.current = callback; });
return useCallback((...args) => ref.current(...args), []);
}5.5. React Compiler ab React 19.2 und ab Next.js 16
Der größte strukturelle Umbruch: Der React Compiler (früher "React Forget") analysiert deinen Code React 19.2 (Seit Oktober 2025) statisch ab und fügt Memoisierung automatisch ein – du schreibst gar kein useCallback/useMemo mehr manuell.
// Mit React Compiler: kein useCallback nötig,
// der Compiler memoisiert automatisch, wo sinnvoll
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return <button onClick={increment}>+</button>;
}5.6. Einfach nichts tun
Die unterschätzteste Alternative: Wenn keine der drei relevanten Situationen vorliegt (React.memo-Kind, useEffect/useMemo-Dependency, Custom-Hook-Rückgabewert), ist eine neue Funktion pro Render schlicht kein Problem. Der Vergleichsaufwand von useCallback selbst kostet mehr, als er einspart.
Faustregel: Bevor du zu
useCallbackgreifst, prüfe erst 1–4 als strukturelle Lösung.useCallbackist eher das Werkzeug für den Fall, dass die Funktion wirklich von Props/State abhängt UND in einem der drei relevanten Kontexte landet.
6. Fazit
Der Vergleich der Dependency-Arrays und das Vorhalten der alten Funktion kosten selbst auch etwas. useCallback lohnt sich konkret bei:
Props an
React.memo-KomponentenDependencies in
useEffect/useMemoReturn-Werten aus Custom Hooks
Ohne einen dieser drei Kontexte ist useCallback in der Regel reiner Overhead ohne Nutzen – eine neue Funktion bei jedem Render zu erzeugen ist für sich genommen extrem billig.
Das mentale Modell:
useCallback()
│
▼
stabile Referenz
│
┌───────────────┼────────────────┬
│ │ │
▼ ▼ ▼
React.memo() Hooks Context
│
▼
1. fn als Dependency
2. fn als RückgabewertHooks sind hier einerseits fn als Dependencies und andereseits custom Hooks die funktionen zurück geben.
Die wichtigsten Merksätze
useMemo() → „Merke dir diesen Wert.“
useCallback() → „Merke dir diese Funktion.“
React.memo() → „Überspringe das Rendering, wenn die Props gleich geblieben sind.“