Back to blogs
Part 2: Optimizing React Re-renders — Memoization and Smarter Components

Part 2: Optimizing React Re-renders — Memoization and Smarter Components

July 15, 2025

Ashish Gogula Ashish Gogula

In Part 1, we talked about why React re-renders and what triggers it.

If you haven’t read it yet, you can check it out here: React Rendering Demystified — What Actually Causes Re-Renders

Now that you understand the “why,” let’s go one step further — how do we stop unnecessary re-renders from slowing down our apps?

Blog Image

This post is all about simple ways to make React faster by telling it:

"Hey, nothing changed here — don’t bother re-rendering this part."

You don’t need to over-optimize or memorize everything. Just get the basics right, and React will take care of the rest.

Table of Contents

  • React.memo – Stop re-rendering the same thing again
  • useCallback – Stop React from remaking the same function
  • useMemo – Remember heavy calculations
  • Split components – Don’t make the whole app re-render
  • Common memo mistakes – Where people go wrong
  • When not to optimize – Sometimes, simple is faster
  • Key takeaways and next steps

React.memo – Stop re-rendering the same thing again

Imagine you have a child component that always gets the same props. But every time the parent changes, the child still re-renders — even though nothing changed for it.

That’s where React.memo comes in. It tells React:

"Hey, if the props are exactly the same, don’t re-run this component. Just reuse the last one."

Here’s how it works:

jsx
const Child = React.memo(function Child({ label }) {
  console.log("Child rendered");
  return <p>{label}</p>;
});

Now, unless label changes, React will skip rendering Child.

Use it for:

  • Components that receive the same props often
  • UI parts that are expensive to render (like charts or long lists)

useCallback – Stop React from remaking the same function

Every time a component re-renders, it also remakes any functions inside it. That’s usually fine — unless you're passing those functions as props to memoized children.

Why? Because even if the function does the same thing, its reference changes, and that makes React think something changed.

useCallback helps by giving you the same function reference between renders

jsx
const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []);

Use it when:

  • You're passing a function to a React.memo-ized child
  • You want to avoid re-triggering effects or renders unnecessarily

useMemo – Remember heavy calculations

If you're doing something slow — like sorting a big array or filtering data — React will do it again every time your component re-renders. That’s wasted work.

useMemo helps you remember the result of a calculation until its inputs change.

jsx
const sortedData = useMemo(() => {
  return expensiveSort(data);
}, [data]);

Now expensiveSort() only runs when data changes — not on every render.

Use it for:

  • Sorting, filtering, or transforming large datasets
  • Anything that's noticeably slow or CPU-heavy

Split components – Don’t make the whole app re-render

Let’s say your Parent component updates often, but your HeavyChild doesn’t need to care. If everything is in one tree, React will keep re-rendering the whole thing.

The fix? Split it.

jsx
function Parent() {
  return (
    <>
      <LightUI />
      <HeavyUI />
    </>
  );
}

Now you can memoize just HeavyUI, and it won’t be touched when LightUI updates.

Use this strategy when:

  • One part of your UI changes often, and the rest doesn’t
  • You want to isolate state and rendering scopes

Common memo mistakes – Where people go wrong

Here are a few traps to avoid:

  • Memoizing everything: Not everything needs it. Small components are usually fast enough.
    • Forgetting dependencies: useMemo and useCallback need clean, correct dependency arrays.
  • Optimizing before measuring: Don’t guess. Use tools (like React DevTools) to see what’s slow.

Memoization is a tool — not a magic spell.

When not to optimize – Sometimes, simple is faster

React is already fast. You don’t need to “optimize” every component.

Don’t use memo, useCallback, or useMemo if:

  • The component is tiny or static
  • You aren’t passing props to children
  • You haven’t noticed any slowdowns

Sometimes, plain React is better than trying to be too clever.

Key takeaways

  • React.memo skips re-renders if props don’t change
  • useCallback helps you pass stable function references
  • useMemo avoids re-running expensive logic
  • Split components to localize updates
  • Always measure first, optimize later

Before we wrap up, let’s see a quick example of everything in action.

Real Example: When Memoization Actually Helps

jsx
import React, { useState, useCallback } from 'react';

// Child component
const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return (
    <button onClick={onClick}>Click from Child</button>
  );
});

// Parent component
export default function Parent() {
  const [parentClicks, setParentClicks] = useState(0);
  const [childClicks, setChildClicks] = useState(0);

  const handleChildClick = useCallback(() => {
    setChildClicks(prev => prev + 1);
  }, []);

  console.log('Parent rendered');

  return (
    <div>
      <h2>Parent Clicks: {parentClicks}</h2>
      <h3>Child Clicks: {childClicks}</h3>

      <button onClick={() => setParentClicks(c => c + 1)}>
        Re-render Parent
      </button>

      <Child onClick={handleChildClick} />
    </div>
  );
}

What’s Happening Here?

Child is wrapped in React.memo so it won’t re-render unless its props change.
handleChildClick is wrapped in useCallback to avoid passing a new function on every render.
Clicking the "Re-render Parent" button updates only the parent — the child does not re-render.
Clicking the "Click from Child" button only affects the child’s state.

What’s next (Part 3)

In the final part, I’ll cover how to actually measure performance, using tools like:

  • React DevTools Profiler
  • why-did-you-render
  • Flame charts
  • Real-world debugging tricks

So you can stop guessing — and start fixing the real bottlenecks.