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 ofperformance.now()
.
Exercises
- Extract Expensive into its own memoized component using
React.memo
; compare renders. - Add a derived value computed from
n
withuseMemo
and 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.