React.memo
React.memo is a Higher-Order Component (HOC) used to optimize performance by memoizing a React component. It prevents unnecessary re-renders if the component's props have not changed.
How it Works
By default, React components re-render whenever their parent re-renders, even if their own props haven't changed. React.memo wraps a component and intercepts the render cycle. Before rendering, it checks the new props against the old props.
- If the props are exactly the same (via shallow equality check), it skips the render and reuses the last rendered result.
- If the props have changed, it allows the render to proceed.
import React from 'react';
const UserProfile = ({ name, age }) => {
console.log("UserProfile rendered");
return <div>{name} - {age}</div>;
};
// Wrap the component with React.memo
export default React.memo(UserProfile);
Shallow Comparison
React.memo uses a shallow comparison (similar to Object.is) to compare props.
- Primitives (
string,number,boolean) are compared by value. - Objects and Arrays are compared by reference.
The Reference Trap
Because objects and arrays are compared by reference, recreating an object in a parent component will break React.memo in the child.
// ParentComponent.jsx
function Parent() {
const [count, setCount] = useState(0);
// ❌ BAD: This object is recreated on every Parent render.
// UserProfile will re-render even though the actual values didn't change.
const user = { name: "John", age: 30 };
// ❌ BAD: Functions are objects too. This inline function creates a new reference.
const handleClick = () => console.log("clicked");
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<UserProfile user={user} onClick={handleClick} />
</div>
);
}
Fixing the Reference Trap
To fix this, combine React.memo with useMemo (for objects/arrays) and useCallback (for functions).
// ParentComponent.jsx
function Parent() {
const [count, setCount] = useState(0);
// ✅ GOOD: Reference remains stable across renders
const user = useMemo(() => ({ name: "John", age: 30 }), []);
// ✅ GOOD: Reference remains stable
const handleClick = useCallback(() => console.log("clicked"), []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<UserProfile user={user} onClick={handleClick} />
</div>
);
}
Custom Comparison Function
If a shallow comparison is not sufficient (e.g., you have a complex nested object prop), you can provide a custom comparison function as the second argument to React.memo.
const areEqual = (prevProps, nextProps) => {
// Return true if passing nextProps would render the same result as prevProps
// Return false if they are different and should re-render
return prevProps.user.id === nextProps.user.id;
};
export default React.memo(UserProfile, areEqual);
Note: Be careful with custom comparators. If the comparison logic is slower than just letting the component re-render, you've degraded performance.
When NOT to use React.memo
React.memo has a cost. React has to run the equality check.
Do not use it if:
- The component frequently receives different props. The equality check will fail often, meaning you pay for the equality check and the re-render.
- The component is cheap to render. Simple UI components (like a button or a badge) render extremely fast. Memoizing them adds unnecessary overhead.
- Children prop: If your component accepts
children, memoization usually fails becausechildren(JSX) creates new references on every render.