A slow React screen often attracts the same first response: add useMemo, wrap components in memo, and stabilize callbacks. Those tools can help, but applying them before measuring creates more comparison work and can leave the actual bottleneck untouched.
Separate frequency from cost
A component rendering often is not automatically a problem. A small component can render hundreds of times without becoming visible in a trace. A chart that renders once may still block the main thread. Use the React Profiler to answer two different questions: which components render during the interaction, and which renders consume meaningful time?
The browser performance panel adds the rest of the picture. It shows long tasks, style calculation, layout, paint, scripting outside React, and network gaps. If React finishes in 8ms but layout takes 140ms, memoizing props is not the solution.
Fix ownership before caching calculations
State placed too high in the tree expands the render surface. Move rapidly changing state closer to the interaction that owns it, or split a stable subtree so it is passed as children. This architectural change often removes more work than a collection of memoization wrappers.
function SearchPage() {
return (
<SearchProvider>
<SearchInput />
<SearchResults />
</SearchProvider>
)
}
The provider should expose the smallest useful values, and consumers should subscribe only to what they need. A single context object containing state, actions, loading flags, and unrelated settings causes every consumer to wake up whenever any part changes.
Memoization has a cost model
memo compares props, useMemo stores a value, and useCallback stores a function reference. Each adds allocation, dependency tracking, and conceptual overhead. They pay off when the avoided work is more expensive than maintaining the cache and when references remain stable enough to hit it.
Optimize the interaction, not the component
For large updates, consider whether every result must render immediately. Virtualize long lists, defer non-urgent updates, paginate data, and move heavy computation off the main thread. React transitions can preserve input responsiveness, but they do not make expensive work free.
My workflow is consistent: reproduce on a production build, record the interaction, identify the longest blocking region, determine whether React owns it, and make one change. Performance improves faster when every optimization begins with evidence and ends with another trace.