Why Your React App Feels Slow: Diagnosing Hidden Rendering Problems
A couple of weeks ago, I was staring at Chrome DevTools and the React Profiler, checking my component performance. My app felt… off. Clicking a dropdown had a tiny delay, and typing in a search field wasn’t as smooth as it should be.
So, I did what any React dev would do: I ran the Profiler.
Nothing. No scary red bars. No ridiculous render times. According to DevTools, everything was fine.
But my gut (and my eyes) told me something was wrong.
That’s when I remembered: some React performance problems never wave a red flag in DevTools. They hide in the background, quietly making your components do extra work for no reason.
In this post, I’ll share a few of those sneaky rendering bottlenecks I’ve run into in real projects, how to detect them, and how to fix them.
Why DevTools Isn’t the Whole Story
DevTools and the React Profiler are amazing. They tell you how long components take to render and which ones re-render during an interaction.
But they won’t tell you:
Think of DevTools like a thermometer: it tells you there’s heat, but not what’s causing the fire.
1. Unstable Function Props
The classic “Why is my memoized child still re-rendering?” problem.
Every time you do:
<Button onClick={() => handleClick(id)} />
You’re creating a brand-new function on every render. React sees a new prop, and boom — re-render.
The fix: Use useCallback memoize handlers:
const handleClickMemo = useCallback(() => handleClick(id), [id]);
<Button onClick={handleClickMemo} />
Now the function reference stays stable unless it id changes.
2. Object & Array Literals in JSX
Same issue, different shape:
<Card style={{ padding: 10 }} />
That It It { padding: 10 } is a new object on every render. Even if nothing changes, I React.memo will think it did.
The fix:
const style = useMemo(() => ({ padding: 10 }), []);
<Card style={style} />
Stable reference, happy component.
3. Context API Overuse
Context is great for avoiding prop drilling — but it’s also a re-render trap.
If you do this:
<UserContext.Provider value={{ user, theme }}>
and theme changes, every component that consumes user will re-render too, even if it doesn’t care about theme.
Recommended by LinkedIn
The fix:
4. State Stored Too High
A subtle one: if your state lives high up in the tree, any update will cascade down.
Example: Your top-level page component stores the open/close state for a dropdown. Changing that state forces the entire page to re-render.
The fix: Keep the state as close as possible to the component that uses it.
5. Derived State Without Memoization
If you’re calculating something from state on every render without caching, you’re wasting CPU cycles.
Example:
const sortedList = bigList.sort();
This sorts on every render, even when bigList hasn’t changed.
The fix:
const sortedList = useMemo(() => bigList.sort(), [bigList]);
Now it only recalculates when necessary.
How to Spot These “Invisible” Bottlenecks
1. Use WhyDidYouUpdate Debug Hook
Logs exactly which props changed between renders:
function useWhyDidYouUpdate(name, props) {
const prevProps = useRef();
useEffect(() => {
if (prevProps.current) {
Object.entries(props).forEach(([key, value]) => {
if (prevProps.current[key] !== value) {
console.log(`${name} prop '${key}' changed`);
}
});
}
prevProps.current = props;
});
}
2. Render Count Debugging
Quick way to see how often something renders:
const renderCount = useRef(0);
console.log('Renders:', ++renderCount.current);
3. React.memo + Stable Props
Memoization only works if your props don’t change identity. Pair React.memo with useCallback and useMemo to make it effective.
4. React Profiler API
Measure render times programmatically:
<Profiler id="SearchBox" onRender={(id, phase, actualDuration) => {
console.log({ id, phase, actualDuration });
}}>
<SearchBox />
</Profiler>
Final Thoughts
If your React app feels slow but DevTools says everything’s fine, trust your instincts. Some bottlenecks don’t leave a trail in the profiler.
Look out for:
#React #Web Performance #Optimization #Performance Optimization #FrontEndDevelopment
Blog Author: Rutik Bhosale