Memoization Pitfalls

+15 Mana ✨

Common mistakes that break memoization.

Pitfall 1: Unstable Dependencies

jsx
// ❌ options is new every render
function Component() {
  const options = { sort: 'asc', limit: 10 };
  
  const data = useMemo(() => {
    return processData(options);
  }, [options]); // Always different!
}

// ✅ Move outside or memoize
const OPTIONS = { sort: 'asc', limit: 10 };

function Component() {
  const data = useMemo(() => {
    return processData(OPTIONS);
  }, []);
}

// ✅ Or memoize the options
function Component({ sort, limit }) {
  const options = useMemo(() => ({ sort, limit }), [sort, limit]);
  
  const data = useMemo(() => {
    return processData(options);
  }, [options]);
}

Pitfall 2: Callback Using Stale Values

jsx
// ❌ count is stale
function Counter() {
  const [count, setCount] = useState(0);
  
  const logCount = useCallback(() => {
    console.log(count); // Always logs initial value
  }, []); // count should be in deps
  
  return (
    <button onClick={logCount}>
      Count: {count}
    </button>
  );
}

// ✅ Include count in dependencies
const logCount = useCallback(() => {
  console.log(count);
}, [count]);

// ✅ Or use a ref for "escape hatch"
function Counter() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  countRef.current = count;
  
  const logCount = useCallback(() => {
    console.log(countRef.current); // Always current
  }, []);
}

Pitfall 3: Premature Memoization

jsx
// ❌ Over-memoizing simple values
function Component({ price, quantity }) {
  // Multiplication is cheap - no need to memoize
  const total = useMemo(() => price * quantity, [price, quantity]);
  
  // String concatenation is cheap
  const message = useMemo(() => `Total: $${total}`, [total]);
  
  return <span>{message}</span>;
}

// ✅ Just calculate inline
function Component({ price, quantity }) {
  const total = price * quantity;
  return <span>Total: ${total}</span>;
}

Pitfall 4: Breaking memo with Spreading

jsx
// ❌ Spreading props breaks memo
function Parent({ user, ...rest }) {
  return <MemoizedChild user={user} {...rest} />;
  // rest is new object every render!
}

// ✅ Be explicit about props
function Parent({ user, onSave, onCancel }) {
  return (
    <MemoizedChild 
      user={user} 
      onSave={onSave}
      onCancel={onCancel}
    />
  );
}

Pitfall 5: Forgetting Children

jsx
// ❌ children breaks memoization
function Parent() {
  return (
    <MemoizedWrapper>
      <Content /> {/* New JSX element every render */}
    </MemoizedWrapper>
  );
}

// ✅ Memoize children too
function Parent() {
  const content = useMemo(() => <Content />, []);
  return <MemoizedWrapper>{content}</MemoizedWrapper>;
}

// ✅ Or use render props
function Parent() {
  return (
    <MemoizedWrapper renderContent={() => <Content />} />
  );
}

Debugging Memoization

jsx
// Add logging to see when memo fails
const MemoizedComponent = memo(
  function Component(props) {
    console.log('Component rendered', props);
    return <div>{/* ... */}</div>;
  },
  (prevProps, nextProps) => {
    const areEqual = shallowEqual(prevProps, nextProps);
    if (!areEqual) {
      console.log('Props changed:', {
        prev: prevProps,
        next: nextProps,
      });
    }
    return areEqual;
  }
);
✓ Completed