
Part 2: Optimizing React Re-renders — Memoization and Smarter Components
Published: 7/15/2025
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?

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:
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
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.
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.
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
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.