
React DevTools: See What Your Components Are Really Doing
Stop guessing why your React app is slow. React DevTools Profiler shows exactly which components re-render and why.

Stop guessing why your React app is slow. React DevTools Profiler shows exactly which components re-render and why.
Class is there, but style is missing? Debugging Tailwind CSS like a detective.

Think Android is easier than iOS? Meet Gradle Hell. Learn to fix minSdkVersion conflicts, Multidex limit errors, Namespace issues in Gradle 8.0, and master dependency analysis with `./gradlew dependencies`.

App crashed with TypeError? Learn why 'Null is not a subtype of String' happens and how to make your JSON parsing bulletproof with Zod/Freezed.

Optimizing by gut feeling made my app slower. Learn to use Performance profiler to find real bottlenecks and fix what matters.

I built a dashboard that displays about 100 items in a list. It ran fine locally, but as the data grew, it got noticeably slower. Every scroll felt janky, and changing the search filter froze the UI for 1-2 seconds.
The problem wasn't that it was slow—the problem was I had no idea where the slowness came from. The code looked fine. I sprinkled console.log everywhere, but I couldn't tell when or why components were re-rendering. I'd log a component render and see it fire constantly, but I had no idea if that was normal or a bug.
Then a coworker asked: "Did you try the DevTools Profiler?"
Oh right. That exists. I'd been using Chrome DevTools exclusively and had installed React DevTools but never really used it. I opened it and saw two tabs: Components and Profiler. I had no idea what the difference was, so I just hit the Profiler first.
I clicked the blue record button in the Profiler tab. Then I changed the search filter in my app. Instantly, a colorful flame graph appeared in DevTools. Each component showed how much rendering time it consumed, represented by bar length.
// The problematic code
function Dashboard() {
const [searchTerm, setSearchTerm] = useState('');
const [items, setItems] = useState([]);
// This object gets recreated every render
const filterConfig = {
caseSensitive: false,
includeArchived: true
};
return (
<div>
<SearchBar value={searchTerm} onChange={setSearchTerm} />
<ItemList items={items} filter={filterConfig} searchTerm={searchTerm} />
</div>
);
}
function ItemList({ items, filter, searchTerm }) {
console.log('ItemList rendered'); // This keeps firing
return (
<div>
{items.map(item => (
<ItemCard key={item.id} item={item} filter={filter} />
))}
</div>
);
}
Looking at the flame graph, ItemList showed up in yellow (moderately slow), and all 100 ItemCard components underneath were gray (rendered). Just changing the search term caused all 100 cards to re-render.
But the real revelation was the "Why did this render?" feature. I clicked on ItemList and saw:
Why did this render?
- Props changed: filter
- Props changed: searchTerm
searchTerm changing made sense, but why filter? Looking back at the code, I realized filterConfig was being recreated every render. In JavaScript, {} !== {}, so React saw it as a prop change every time. This was the culprit I'd been hunting for.
Now that the Profiler found the culprit, it was time to fix it. But before fixing anything, I explored the Components tab. This showed my app's component tree, laid out like an HTML structure with the full component hierarchy.
Clicking on ItemList showed all its props, state, and hooks on the right side. Clicking the filter prop let me inspect the actual object contents. The most useful part was the "rendered by" information—I could trace exactly which parent component triggered the render.
I turned on "Highlight updates when components render" in the DevTools settings, and suddenly everything became visible. Every time I changed the search term, blue borders flashed on screen, showing exactly which parts were re-rendering in real-time. Watching all 100 cards flash convinced me: "This needs fixing."
With DevTools pinpointing the exact problem, fixing it was straightforward. I used a three-step approach:
Step 1: Remove unnecessary object creationfunction Dashboard() {
const [searchTerm, setSearchTerm] = useState('');
const [items, setItems] = useState([]);
// Memoize the object so it's not recreated
const filterConfig = useMemo(() => ({
caseSensitive: false,
includeArchived: true
}), []); // No dependencies = created only once
return (
<div>
<SearchBar value={searchTerm} onChange={setSearchTerm} />
<ItemList items={items} filter={filterConfig} searchTerm={searchTerm} />
</div>
);
}
Step 2: Prevent unnecessary re-renders
// React.memo ensures re-render only when props actually change
const ItemCard = React.memo(({ item, filter }) => {
return (
<div className="card">
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
});
Step 3: Verify with Profiler
I opened the Profiler again and repeated the same interaction. This time, all 100 ItemCard components showed "Did not render" instead of gray. When the search term changed but the filter didn't, the cards simply didn't re-render. The flame graph bars were much shorter.
The perceived speed difference was dramatic. What used to take 1-2 seconds when changing filters now responded instantly. Scrolling became noticeably smoother.
This experience taught me that performance optimization isn't about intuition—it's about data.
Looking at code alone, you can only guess "this part seems slow." React DevTools turned those guesses into certainty. The Profiler showed "which components are slow," the Components tab explained "why they rendered," and Highlight updates revealed "what's actually happening" in real-time.
Initially, I wanted to wrap every list component in React.memo. Classic over-optimization. But measuring with the Profiler revealed that only 2-3 components were actually problematic. The rest rendered fast enough even without memoization. Without DevTools, I would've littered the codebase with unnecessary memoization.
React DevTools is like a CT scanner for doctors. From the outside, you know "something hurts somewhere," but the scan shows exactly what's wrong. Now when I face performance issues, I open DevTools before diving into code. Five minutes of profiling saves five hours of guesswork.