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 useRefto keep a render counter that persists across renders without causing re-renders.
- performance.now()measures durations with sub-millisecond resolution.
- React.useMemocan 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 ofperformance.now().
Exercises
- Extract Expensive into its own memoized component using React.memo; compare renders.
- Add a derived value computed from nwithuseMemoand show timing differences.
- Introduce a list of 500 items and measure filtering time; then optimize with memoization.
Checks for Understanding
- What measurements help you decide where to optimize?
- How does memoization affect render counts and timings?
Show answers
- Render counts for hot components and interaction timings for critical paths.
- It can reduce re-renders and shorten timings when inputs don’t change, but adds overhead if overused.