🧠 Optimize React Rendering Like a Pro
Eliminate unnecessary re-renders and make your UI fly!
React is fast… until it isn’t. While React’s virtual DOM and reconciliation algorithms are designed for efficiency, certain patterns in your app can lead to excessive, unnecessary re-renders that hurt performance—especially on slower devices.
In this guide, you’ll learn:
memo
, custom comparators, and props restructuringLet’s dive deep and make your components render smarter—not harder. 🚀
Understanding React’s internal phases helps us understand when and why components re-render:
Render Phase
React creates virtual DOM elements via React.createElement
.
Reconciliation Phase React compares the new elements with the previous render to detect changes.
Commit Phase React updates the actual DOM (if anything changed).
Updating the DOM is expensive, so React avoids it unless necessary. But rendering and reconciling can also become bottlenecks—especially when they happen more often than they need to.
A React component will re-render if:
If a component re-renders but its output hasn’t changed, that’s an unnecessary re-render.
memo
Let’s take a simple app as an example:
function CountButton({ count, onClick }) {return <button onClick={onClick}>{count}</button>}function NameInput({ name, onNameChange }) {return (<label>Name: <input value={name} onChange={(e) => onNameChange(e.target.value)} /></label>)}function App() {const [name, setName] = useState('')const [count, setCount] = useState(0)return (<div><CountButton count={count} onClick={() => setCount(c => c + 1)} /><NameInput name={name} onNameChange={setName} />{name && <p>{name}'s favorite number is {count}</p>}</div>)}
Every time you click the button, NameInput
re-renders—even though its props haven’t changed! Why?
Because the App
component re-renders, and React has no way of knowing whether child components need to re-render or not.
React.memo
const NameInput = React.memo(function NameInput({ name, onNameChange }) {return (<label>Name: <input value={name} onChange={(e) => onNameChange(e.target.value)} /></label>)})
Now, NameInput
will only re-render if name
or onNameChange
changes.
Don’t wrap everything in memo
. It adds complexity and might not improve performance if your components are already cheap to render.
CountButton
?Try wrapping CountButton
in memo
:
const CountButton = React.memo(function CountButton({ count, onClick }) {return <button onClick={onClick}>{count}</button>})
💥 Surprise: It still re-renders!
Why? Because onClick
is a new function instance on every render:
const increment = () => setCount(c => c + 1)
So React sees a prop change and re-renders anyway.
useCallback
const increment = useCallback(() => setCount(c => c + 1), [])
Now, onClick
doesn’t change across renders—and memo
works.
memo
Sometimes, even if a prop object is a new reference, its contents haven’t changed. memo
doesn’t know that by default—it just does shallow comparison.
So you can pass a custom comparator:
const Avatar = memo(function Avatar({ user }: { user: User }) {return <img src={user.avatarUrl} alt={user.name} />},(prevProps, nextProps) => (prevProps.user.avatarUrl === nextProps.user.avatarUrl &&prevProps.user.name === nextProps.user.name))
Now the component only re-renders when relevant properties change.
If possible, restructure props to avoid custom comparators:
const Avatar = memo(function Avatar({ avatarUrl, name }) {return <img src={avatarUrl} alt={name} />})
Now React’s default shallow comparison works perfectly—no need for a custom function.
To see what’s really going on under the hood:
Use the React DevTools Profiler
Use Chrome DevTools Performance Tab
🔬 Optimization is only useful if it fixes a real bottleneck.
Optimization | Use When… |
---|---|
React.memo | Child component receives stable props |
useCallback | You pass a function prop to a memo component |
Custom comparator | Props are objects that change by reference only |
Primitive props | You want to avoid writing custom comparators |
React Profiler | You want to confirm actual render performance |
Before you reach for memo
, fix the slow render first. Memoization only helps avoid work—if the work is still slow, it won’t help.
Check out this article for a deeper look: 👉 Fix the slow render before you fix the re-render
React is powerful, but optimization takes intent. Profile first, measure, then optimize.
Quick Links
Legal Stuff
Social Media