≡ Menu

React Performance: Mastering memo and useCallback

In the world of React, every time a parent component’s state changes, React’s default behavior is to re-render every single one of its children.

In small apps, this is fine. In large apps, this leads to “laggy” interfaces.

Today, we’ll look at how to stop unnecessary renders using your provided code as a master blueprint.


The Problem: The “Default” Render Behavior

Imagine clicking a “Counter” button. The number goes up, but your huge list of 1,000 items also refreshes—even though the list didn’t change!

This happens because React recreates everything inside the Parent function on every render, including new instances of your functions.


Step 1: React.memo (The Shield)

The first line of defense is memo. It tells React: “Only re-render this child if its props have actually changed.”

const List = memo(({ items, onItemClick }) => {
  console.log("List rendered"); // This will now only log when absolutely necessary
  return (
    <ul>
      {items.map(item => (
        <li key={item} onClick={() => onItemClick(item)}>{item}</li>
      ))}
    </ul>
  );
});

What happens now?

When the count state in the Parent changes, React looks at the List. It checks the items prop and the onItemClick prop.

If they look the same as last time, it skips the work.


Step 2: useCallback (The Anchor)

Even with memo, the List might still re-render.

Why?

Because of Referential Equality.

In JavaScript, function() {} === function() {} is false.

Every time Parent renders, handleItemClick is a “brand new” function in memory.

To memo, a new function means a changed prop.

We fix this with useCallback:

// Inside Parent component
const handleItemClick = useCallback((item) => {
  console.log("Clicked:", item);
}, []); // The empty array ensures this function instance is created ONLY ONCE

useCallback “anchors” the function. It tells React: “Keep this exact same version of the function in memory across all future renders.”


Step 3: The Connection (onItemClick={handleItemClick})

This is where the magic happens. In your JSX, you pass the anchored function to the shielded component:

<List items={items} onItemClick={handleItemClick} />
  1. The User Clicks: When a user clicks “Banana”, the li triggers the anonymous function () => onItemClick(item).

  2. The Bridge: onItemClick is just a reference (a pointer) to handleItemClick.

  3. The Result: The code in the Parent executes. Because the reference never changed, the List stayed perfectly still while the count updated.


When Should You Use This?

Don’t wrap everything! Use this “Power Couple” when:

  • The child component (List) is expensive to render (has lots of elements or complex logic).

  • The child component is wrapped in memo.

  • The function is passed as a dependency to a useEffect inside a child.


Summary Table

Tool Role What it prevents
React.memo Component Wrapper Re-rendering the child if props stay the same.
useCallback Hook Re-creating the function instance on every render.
[] Dependencies Guard Rails Prevents the function from updating unless specific values change.

Useful links below:

Let me & my team build you a money making website/blog for your business https://bit.ly/tnrwebsite_service

Get Bluehost hosting for as little as $1.99/month (save 75%)…https://bit.ly/3C1fZd2

Best email marketing automation solution on the market! http://www.aweber.com/?373860

Build high converting sales funnels with a few simple clicks of your mouse! https://bit.ly/484YV29

Join my Patreon for one-on-one coaching and help with your coding…https://www.patreon.com/c/TyronneRatcliff

Buy me a coffee ☕️https://buymeacoffee.com/tyronneratcliff

{ 0 comments… add one }

Leave a Comment