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.memo
remembers 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.memo
without 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.memo
will catch changes in deeply nested objects or arrays. - Consequence: Since
React.memo
uses a shallow comparison by default, changes in nested data structures might not trigger a re-render. Use custom comparison functions or memoize the data withuseMemo
before 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
useCallback
or objects withuseMemo
.
- Relying on React.memo for Components with Internal State or Context:
- Mistake: Believing that
React.memo
can optimize a component that frequently updates its own state or consumes context values that change over time. - Consequence: Since
React.memo
only 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.memo
only 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.memo
may 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.memo
will 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.memo
does not control.
- Mistake: Expecting that