Fossils⚛ïļ React PatternsReact Render Optimization Patterns
ðŸĶ–DinosaurReactPerformanceArchitecture

React Render Optimization Patterns

When React re-renders too much, most developers reach for React.memo. Seniors reach for architecture.

React Render Optimization Patterns

The question "How do you optimize React rendering?" is deceptively simple. The answer reveals your depth.

Understanding When React Re-renders

A component re-renders when:

  1. Its state changes
  2. Its parent re-renders (props may or may not have changed)
  3. The context it consumes changes

React does NOT re-render because:

  • A DOM event fires
  • A ref changes
  • A variable outside state changes

The Optimization Hierarchy

Level 1: Move State Down

The cheapest optimization — co-locate state with the component that uses it.

// Before: entire page re-renders on hover
function Page() {
  const [hovered, setHovered] = useState(false);
  return (
    <div>
      <ExpensiveHeader />
      <HoverCard onHover={setHovered} hovered={hovered} />
      <ExpensiveFooter />
    </div>
  );
}
 
// After: only HoverCard re-renders
function Page() {
  return (
    <div>
      <ExpensiveHeader />
      <HoverCard />
      <ExpensiveFooter />
    </div>
  );
}

Level 2: Composition — Children as Props

function ScrollTracker({ children }) {
  const [scrollY, setScrollY] = useState(0);
  useEffect(() => {
    const handler = () => setScrollY(window.scrollY);
    window.addEventListener('scroll', handler);
    return () => window.removeEventListener('scroll', handler);
  }, []);
 
  return (
    <div>
      <ScrollIndicator position={scrollY} />
      {children}
    </div>
  );
}

children was created by the parent, so it won't re-render when scrollY changes.

Level 3: React.memo (Use Sparingly)

const ExpensiveList = React.memo(function ExpensiveList({ items }) {
  return items.map(item => <ListItem key={item.id} {...item} />);
});

Only use when the component is expensive AND its parent re-renders frequently with unchanged props.

Level 4: useMemo / useCallback

const sortedItems = useMemo(
  () => items.sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);
 
const handleClick = useCallback(
  (id) => dispatch({ type: 'SELECT', id }),
  [dispatch]
);

The Anti-Patterns

  1. Memoizing everything — adds complexity, memory overhead, and often doesn't help
  2. Optimizing without measuring — use React DevTools Profiler first
  3. Fixing symptoms not causes — a component re-rendering 100 times means the architecture is wrong

The senior approach: design component boundaries so re-renders are naturally scoped. Memoization is a last resort, not a first tool.