Avoiding Re-Renders

Avoiding Unnecessary Re-Renders

A component renders again whenever its parent does, even with identical props. The demo's button appends a letter to state in Perf, so both lists render again on every tap. The plain rows receive a new arrow function each time; the memoized rows receive one stable function from useCallback:

src/demos/Perf.tsx: the same rows, plain and memoized (excerpt)TSX
const MemoRow = memo(Row);
// ...
function PlainList({ letter }: { letter: string }) {
  return books.map((b) =>
    <Row key={b.id} book={b} list="plain" onPress={(id) => console.log(letter, id)} />);
}
function MemoList() {
  const onPress = useCallback((id: number) => console.log(id), []);  // the same every time
  return books.map((b) => <MemoRow key={b.id} book={b} list="memo" onPress={onPress} />);
}

Three taps logged:

Output of 242
 LOG  plain update: 6 rows rendered in 12.2 ms
 LOG  memo update: 0 rows rendered in 0.9 ms
...
 LOG  plain update: 6 rows rendered in 23.7 ms
 LOG  memo update: 0 rows rendered in 1.0 ms

memo skipped all six memoized rows, because book and onPress were the same objects as last time. Pass onPress={(id) => ...} to MemoRow instead and memo would see a new function each time, render every row again, and add its comparison as pure overhead. The same rule covers objects and arrays created during render (style={{ ... }}, data={items.filter(...)}): wrap them in useMemo or move them out of the component. Keep fast-changing state in the smallest component that needs it, and read stores through selectors, as the tab bar reads only the cart count (Zustand Slices).