MediumMemoization🐛 Debug Challenge

React.memo useless without stable callback prop

Buggy Code — Can you spot the issue?

function shallowEqual(a, b) {
  return Object.keys(a).every(k => a[k] === b[k]);
}

let childRenders = 0;
function Child({ onClick, label }) {
  childRenders++;
  return label;
}
function MemoChild(props) {
  if (MemoChild._prev && shallowEqual(MemoChild._prev, props)) return 'skip';
  MemoChild._prev = props;
  return Child(props);
}

// Parent re-renders 3 times, creating a new function each time
for (let i = 0; i < 3; i++) {
  const handleClick = () => console.log('clicked'); // new function each render!
  MemoChild({ onClick: handleClick, label: 'btn' });
}

console.log(childRenders); // should be 1, is 3

Fixed Code

function shallowEqual(a, b) {
  return Object.keys(a).every(k => a[k] === b[k]);
}

let childRenders = 0;
function Child({ onClick, label }) {
  childRenders++;
  return label;
}
function MemoChild(props) {
  if (MemoChild._prev && shallowEqual(MemoChild._prev, props)) return 'skip';
  MemoChild._prev = props;
  return Child(props);
}

// Fix: stable function reference (created once, reused)
const stableHandleClick = () => console.log('clicked'); // useCallback([])

for (let i = 0; i < 3; i++) {
  MemoChild({ onClick: stableHandleClick, label: 'btn' });
}

console.log(childRenders);

Bug Explained

Bug: handleClick is recreated on every render — new function reference each time. shallowEqual sees onClick changed (old fn !== new fn) and re-renders.

Explanation: stableHandleClick is the same reference each render. shallowEqual sees onClick === onClick — child is skipped on renders 2 and 3.

Key Insight: React.memo is only effective when ALL props are stable. Inline arrow functions break it. Wrap callbacks with useCallback to get stable references: const handleClick = useCallback(() => {}, []).

Practice spotting bugs live →

38 debug challenges with AI hints

🐛 Try Debug Lab