useCallback & Function Stability
MeduseCallback returns a memoized version of a callback function that only changes when its dependencies change. It is primarily useful when passing callbacks to memoized child components or when functions appear in effect dependency arrays, preventing unnecessary re-renders or effect re-runs.
Interactive Visualization
Key Points
- useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)
- Without useCallback, a new function reference is created every render
- Primary use: prevent re-renders of React.memo children receiving callback props
- Secondary use: stabilize functions used in useEffect dependency arrays
- Only beneficial when paired with React.memo or dependency arrays
- Adds overhead from closure creation and dependency comparison
Code Examples
Preventing Child Re-renders
import { useState, useCallback, memo } from 'react' interface ItemProps { id: string name: string onDelete: (id: string) => void } const ExpensiveItem = memo(function ExpensiveItem({ id, name, onDelete, }: ItemProps) { return ( <div> <span>{name}</span> <button onClick={() => onDelete(id)}>Delete</button> </div> ) }) function ItemList({ items }: { items: { id: string; name: string }[] }) { const [selected, setSelected] = useState<string | null>(null) // Without useCallback, onDelete changes every render, // defeating React.memo on ExpensiveItem const handleDelete = useCallback((id: string) => { // delete logic here }, []) return ( <div> <p>Selected: {selected}</p> {items.map((item) => ( <ExpensiveItem key={item.id} id={item.id} name={item.name} onDelete={handleDelete} /> ))} </div> ) }
useCallback keeps the same function reference so ExpensiveItem wrapped in React.memo can skip re-rendering when selected changes
Stable Function in Effect Dependencies
import { useState, useCallback, useEffect } from 'react' function SearchResults({ query }: { query: string }) { const [results, setResults] = useState<string[]>([]) const fetchResults = useCallback(async () => { const res = await fetch(`/api/search?q=${query}`) const data = await res.json() as string[] setResults(data) }, [query]) useEffect(() => { fetchResults() }, [fetchResults]) return ( <ul> {results.map((r, i) => <li key={i}>{r}</li>)} </ul> ) }
useCallback makes fetchResults change only when query changes, so the effect does not re-run on every render
Common Mistakes
- Using useCallback on every function even when no child uses React.memo
- Forgetting to include all used variables in the dependency array
- Using useCallback without React.memo on children, providing no benefit
Interview Tips
- Explain that useCallback without React.memo on children is pointless overhead
- Know that useCallback(fn, deps) is syntactic sugar for useMemo(() => fn, deps)
- Discuss the upcoming React Compiler which auto-memoizes and may make useCallback unnecessary