Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
useCallback memoizes a function so that the same function reference is returned across renders as long as its dependencies don't change. The reason this matters: React.memo on a child component does a shallow comparison of props, and a new function created on every render is always a different reference even if it does the same thing. Without useCallback, memoizing a child component is useless the moment you pass it any callback prop. The flip side: useCallback has its own overhead, and memoizing a function whose parent isn't even memoized wastes that overhead entirely.
A function defined inside a component body is a new object every render. useCallback returns the same object as long as its deps haven't changed — critical for preventing React.memo from bailing out.
memo() from ExpensiveChild but keep useCallback in ParentGood. Increment the counter and observe that the child still re-renders. This confirms that useCallback alone does nothing — it only has value when paired with memo (or used as a useEffect dependency).handleAction to depend on count: useCallback(() => console.log('action', count), [count]). Now click the counter — the child should re-render every time because count is a dependency and changes. This shows the dependency cost of useCallback.import { useState, useCallback, memo } from 'react';
// Child only re-renders when its props change
const ExpensiveChild = memo(({ onAction }: { onAction: () => void }) => {
console.log('child rendered');
return <button onClick={onAction}>Act</button>;
});
// WITHOUT useCallback: child re-renders on every parent render
export function ParentBad() {
const [count, setCount] = useState(0);
// New function reference every render → memo is useless
const handleAction = () => console.log('action');
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Parent count: {count}</button>
<ExpensiveChild onAction={handleAction} />
</div>
);
}
// WITH useCallback: child skips re-render when parent count changes
export function ParentGood() {
const [count, setCount] = useState(0);
// Stable reference — deps array is empty, never changes
const handleAction = useCallback(() => console.log('action'), []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Parent count: {count}</button>
<ExpensiveChild onAction={handleAction} />
</div>
);
}