17/25 lessons68%

Avoiding Unnecessary Renders (Without Overthinking It)

Sometimes React apps feel fast—until they don’t. You make a small change, maybe add a new feature or pass one extra prop, and suddenly, your UI feels sluggish. Been there. I once turned a snappy dashboard into a stuttering mess just by nesting a few components and forgetting to memoize properly. What followed was hours spent squinting at DevTools trying to figure out why things kept re-rendering. If you’ve ever wanted to know how to actually avoid unnecessary renders in React, this is the guide I wish someone had handed me back then.

Render Flow Debugging Checklist (AKA “Why is this re-rendering again?”)

First things first—before you try fixing performance, you need to see what’s going on.

Step 1: Enable React’s render highlighting

In React DevTools, flip on “Highlight updates when components render.” It’s subtle, but it shows you exactly which components are lighting up when state or props change.

Step 2: Add `console.log()` strategically

I’m not above a good ol’ console.log('ComponentName rendered'). It’s fast and tells you if a component is rendering more than you think.

Step 3: Use the Profiler tab

This is where the magic happens. It shows you how long each component took to render and why it rendered. You can even track interactions to see how user actions affect performance.

React DevTools Profiler is your MRI scanner for re-renders. Learn it. Use it. Love it. (If you’ve never opened it, this guide from React docs is a good start.)

Memoize Child Components Properly (Don't Wrap Everything)

Let’s talk about the common blunder: memoizing everything like you’re bubble-wrapping a toddler.

Here’s a typical situation:

javascript
1
2
3
4
          const Avatar = ({ user }) => {
  console.log("Rendered Avatar");
  return <img src={user.avatarUrl} alt={user.name} />;
};
        

If this component is used inside a list and the parent updates often, Avatar will re-render each time—even if the user data didn’t change.

Wrap it in React.memo:

javascript
1
2
3
4
          const Avatar = React.memo(({ user }) => {
  console.log("Rendered Avatar");
  return <img src={user.avatarUrl} alt={user.name} />;
});
        

Now, it’ll only re-render when user changes.

But here's the catch: don't memoize components that already render quickly or don’t receive complex props. If you're memoizing a <Label> with just a text prop, you’re adding complexity for nothing. I once did this across a whole codebase. It made things slower and harder to debug. Total backfire.

⚔️ Avoiding Inline Functions (Without Overdoing It)

Inline functions are a sneaky performance killer—only when passed to memoized components.

Let’s say you’ve got:

javascript
1
          <UserCard onClick={() => doSomething(user.id)} />
        

This creates a new function every render. So even if UserCard is memoized, React sees a “new” prop and re-renders it anyway.

Fix:

javascript
1
2
3
4
5
          const handleClick = useCallback(() => {
  doSomething(user.id);
}, [user.id]);

<UserCard onClick={handleClick} />
        

Much better.

That said—don’t go wild creating `useCallback` for every function. If it’s not passed down or doesn't affect re-renders, leave it inline. Otherwise you’re memoizing for no reason. It’s like labeling your kitchen towels. Nice idea. Complete waste of energy.

useMemo for Expensive Calculations (Not for Everything)

Memoization is for expensive things. Let me repeat: expensive.

If you’re memoizing a sum([1, 2, 3]), you’re wasting your time.

Here’s where useMemo actually makes sense:

javascript
1
2
3
          const filteredPosts = useMemo(() => {
  return posts.filter(p => p.published && p.author === user.id);
}, [posts, user.id]);
        

This is valuable if:

`posts` is a large array The calculation is heavy * The parent component renders frequently

If none of that applies? Ditch the useMemo. It’s not a silver bullet—it’s a scoped caching trick.

A mistake I made early on? Memoizing everything. The code got harder to read, bugs got sneakier, and performance got worse. Turns out, caching unnecessary stuff is still work. React doesn’t skip the math—it just delays it.

🧪 Testing Performance in Dev and Prod (Because It’s Not Always the Same)

One of the weirdest things about React optimization is that dev mode lies to you.

In development:

Components render more often (on purpose) Warnings and checks slow things down * Your app feels heavier than it is

So don’t trust dev mode performance for final judgment. Instead:

1. Test in production build:

bash
1
2
             npm run build
   serve -s build
        

2. Use the React Profiler again in this mode.

3. Add RUM (Real User Monitoring) tools like web-vitals or Sentry to get field data.

This way, you’re optimizing what users actually experience, not just what your dev tools say.

And if you’re wondering how to handle things when it all goes wrong—like when a bug crashes part of the tree and breaks the whole app—you’ll want to check out the next lesson: **What Are Error Boundaries and Why You Need Them**. Because React won’t save you from yourself when something explodes mid-render.