React - Performance Profiling

Overview

Estimated time: 20–30 minutes

Measure render counts and interaction timings to find hotspots and verify optimizations.

Try it: Count renders and time interactions

View source
function useRenderCount(name){
  const ref = React.useRef(0); ref.current++; return `${name} renders: ${ref.current}`;
}
function Expensive({n}){
  const label = useRenderCount('Expensive');
  const t0 = performance.now();
  while(performance.now() - t0 < 8) {/* busy work ~8ms */}
  return <div>{label} (n={n})</div>;
}
function App(){
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const [logs, setLogs] = React.useState([]);
  function time(fn, label){
    const t0 = performance.now(); fn(); const ms = Math.round(performance.now()-t0);
    setLogs(ls => [...ls, `${label}: ${ms}ms`]);
  }
  return (
    <div>
      <div style={{display:'flex', gap:8}}>
        <button onClick={() => time(() => setN(x=>x+1), 'inc n')}>Inc n</button>
        <button onClick={() => time(() => setM(x=>x+1), 'inc m')}>Inc m</button>
      </div>
      <p>Note: Expensive depends only on n; changing m should not re-render it when memoized.</p>
      <Expensive n={n} />
      <hr />
      <h4>Memoized version</h4>
      {React.useMemo(() => <Expensive n={n} />, [n])}
      <h4>Timings</h4>
      <ul>{logs.map((l,i)=><li key={i}>{l}</li>)}</ul>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('try-prof')).render(<App />);

Syntax primer

  • Use useRef to keep a render counter that persists across renders without causing re-renders.
  • performance.now() measures durations with sub-millisecond resolution.
  • React.useMemo can memoize expensive subtrees when inputs don’t change.

Vocabulary

  • Hot path: code that runs frequently or takes noticeable time.
  • Memoization: caching results until inputs change.
  • Rendering cost: time spent reconciling and painting component output.

Common pitfalls

  • Premature optimization; measure first and optimize the biggest wins.
  • Overusing memoization—can add complexity and memory overhead.
  • Measuring with Date.now() (coarser) instead of performance.now().

Exercises

  1. Extract Expensive into its own memoized component using React.memo; compare renders.
  2. Add a derived value computed from n with useMemo and show timing differences.
  3. Introduce a list of 500 items and measure filtering time; then optimize with memoization.

Checks for Understanding

  1. What measurements help you decide where to optimize?
  2. How does memoization affect render counts and timings?
Show answers
  1. Render counts for hot components and interaction timings for critical paths.
  2. It can reduce re-renders and shorten timings when inputs don’t change, but adds overhead if overused.