Look, let’s be honest. Working with React is often a joy, but sometimes (or many times?), the performance gotchas make you want to scream (and bang your head on the wall). You write “clean code”, you use your “hooks” correctly, and yet, your application feels sluggish because some overly-eager component is re-rendering every time its parent sneezes, even if its own props haven’t changed. Why, oh why, must React make us jump through these hoops? It’s like having a brilliant assistant who insists on tidying the desk every five seconds, even if nothing’s moved. Yeah…
The core of the issue is React’s default behavior. When a parent component updates (due to state or prop changes), it recursively renders all of its child components.
“A change in the parent doesn’t affect the child’s output? Too bad! Rerender time!” – The React Engine … probably.
This constant, often redundant rendering, especially in complex component trees, is what tanks performance. It forces the Virtual DOM to recalculate, causing measurable latency. We shouldn’t have to constantly babysit the rendering process, but here we are.
To combat this, React gives us React.memo, a Higher-Order Component (HOC) that reluctantly admits that sometimes, a component’s render output is stable given stable props.
How to use it?
You simply wrap your functional component with it:
JavaScript
import React from 'react';
// The component that we want to protect from unnecessary rerenders
const MyAnnoyingChild = ({ data, onClick }) => {
// We only want this console.log to fire when 'data' or 'onClick' actually change
console.log('Rendering MyAnnoyingChild (AGAIN!)');
return <div onClick={onClick}>{data}</div>;
};
// Wrap it in memo
const MemoizedChild = React.memo(MyAnnoyingChild);
// Now, use MemoizedChild in the parent instead of MyAnnoyingChild
export default MemoizedChild;
When a component is wrapped in React.memo, React performs a shallow comparison of the component’s previous props and its new props before deciding whether to rerender.
- If the shallow comparison determines all props are the same (using strict equality), the render process is skipped, and the last rendered result is reused. This is the optimization.
- If any prop is different, the component rerenders as normal.
This is where the complaining comes back. Shallow comparison is great for primitive values like strings and numbers, but it’s a disaster for objects, arrays, and functions (the typical props you pass around).
Why? Because in JavaScript, a new object/array/function literal is not strictly equal to its previous version, even if the contents are identical.
JavaScript
// In a parent component during every render:
const newObj = { count: 1 }; // This creates a NEW object in memory
const oldObj = { count: 1 };
console.log(newObj === oldObj); // **FALSE**!
// React.memo sees a "new" prop and rerenders the child component anyway.
To make memo truly effective when passing complex data or callbacks, we must resort to more performance workarounds that shouldn’t be necessary in the first place: useMemo to stabilize object and array props, and useCallback to stabilize function props.
In short, React.memo works by letting us opt-out of React’s default, often wasteful, rendering cycle, but only if we jump through the extra hoops of ensuring all the component’s props are referentially stable ourselves. It’s a useful tool, but it serves as a constant reminder that React forces us to be deeply concerned with rendering overhead, which is a major pain.
But it does not always work? Why? Because React, react, reaaaact!
Considering all the extra tools, concepts, and mental overhead required for optimization like this, why do we like to complicate software?
Wanna use React?
The choice is yours.
Leave a comment