What is React.memo?
React.memo is a higher-order component (HOC) that optimizes functional components by preventing unnecessary re-renders.
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)
Why Use React.memo?
By default, when a parent component re-renders, all its child components re-render, even if their props haven’t changed.
React.memo prevents these unnecessary re-renders by memoizing the component and only re-rendering it when its props change.
How to Use React.memo?
Without React.memo (Unoptimized)
In this example, ChildComponent re-renders every time ParentComponent updates, even if the name prop hasn’t changed.
import React, { useState } from "react";
const ChildComponent = ({ name }: { name: string }) => {
console.log("🔄 ChildComponent Rendered");
return <h2>Hello, {name}!</h2>;
};
const ParentComponent: React.FC = () => {
console.log("🔄 ParentComponent Rendered");
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
{/* ChildComponent re-renders even when name doesn't change! */}
<ChildComponent name="John" />
</div>
);
};
export default ParentComponent;
Console Output (Without React.memo)
ParentComponent Rendered ChildComponent Rendered ParentComponent Rendered ChildComponent Rendered // Unnecessary re-render!
Even though name="John" hasn’t changed, ChildComponent still re-renders!
Optimized: Using React.memo
Now, we wrap ChildComponent in React.memo, so it only re-renders when its props change.
import React, { useState, memo } from "react";
const ChildComponent = memo(({ name }: { name: string }) => {
console.log("🔄 ChildComponent Rendered");
return <h2>Hello, {name}!</h2>;
});
const ParentComponent: React.FC = () => {
console.log("🔄 ParentComponent Rendered");
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
{/* ChildComponent will NOT re-render unless 'name' changes */}
<ChildComponent name="John" />
</div>
);
};
export default ParentComponent;
Console Output (With React.memo)
ParentComponent Rendered ChildComponent Rendered ParentComponent Rendered
Now, ChildComponent doesn’t re-render when count updates!
How React.memo Works
- Memoization:
React.memoremembers the last rendered output. - Shallow Prop Comparison: It only re-renders if the props have changed (using a shallow equality check).
- Prevents Unnecessary Renders: If the props remain unchanged, React skips re-rendering the component.
When to Use React.memo?
Use React.memo when:
- The component receives the same props frequently.
- The component is expensive to render (e.g., complex calculations, large UI updates).
- The component doesn’t rely heavily on internal state or context for its rendered output.
Don’t use React.memo when:
- The component relies on hooks like
useState,useEffect, or context (useContext) that might change independently of its props. - The component has frequently changing props (e.g., dynamic data from an API).
- The re-rendering cost is low, making memoization unnecessary.
Advanced: Custom Comparison Function
By default, React.memo performs a shallow comparison of props. If you need custom comparison logic, pass a comparison function:
const ChildComponent = memo(
({ name, age }: { name: string; age: number }) => {
console.log("🔄 ChildComponent Rendered");
return <h2>{name}, Age: {age}</h2>;
},
(prevProps, nextProps) => {
// Only re-render if 'name' changes; ignore changes in 'age'
return prevProps.name === nextProps.name;
}
);
Now, ChildComponent only re-renders if name changes, ignoring age.
Common Mistakes with React.memo
Using React.memo effectively requires an understanding of its limitations. Here are some common mistakes:
- Overusing React.memo:
- Mistake: Wrapping every functional component with
React.memowithout evaluating whether the component is expensive to re-render. - Consequence: This can add unnecessary complexity and might even introduce performance overhead in scenarios where re-rendering cost is minimal.
- Mistake: Wrapping every functional component with
- Shallow Comparison Pitfalls:
- Mistake: Assuming that
React.memowill catch changes in deeply nested objects or arrays. - Consequence: Since
React.memouses a shallow comparison by default, changes in nested data structures might not trigger a re-render. Use custom comparison functions or memoize the data withuseMemobefore passing it as props.
- Mistake: Assuming that
- Ignoring Functions as Props:
- Mistake: Passing inline functions or objects as props without memoization.
- Consequence: Every render creates a new function or object reference, which causes the component to re-render even if the actual values have not changed. To avoid this, wrap functions with
useCallbackor objects withuseMemo.
- Relying on React.memo for Components with Internal State or Context:
- Mistake: Believing that
React.memocan optimize a component that frequently updates its own state or consumes context values that change over time. - Consequence: Since
React.memoonly checks props, any changes in internal state or context will still trigger a re-render regardless of memoization. - Explanation of “It won’t work if the component depends on state or context”:
React.memoonly prevents re-renders based on prop changes. If your component relies on its own internal state (usinguseState) or values provided via React Context (useContext), those changes are not controlled by props. This means that even if the props remain unchanged, a change in state or context will force the component to re-render. In such cases, the benefits ofReact.memomay be limited or nullified because the component’s output depends on more than just its props.
- Mistake: Believing that
- Misunderstanding When Re-renders Occur:
- Mistake: Expecting that
React.memowill completely eliminate all re-renders of a component. - Consequence: Developers might be surprised when the component still re-renders because of parent state updates, context changes, or local state updates that
React.memodoes not control.
- Mistake: Expecting that
