When the state of a React component changes, the component and all of its children will re-render. While this is fine for most cases, you may have a child component that does some heavy computation, and you may want it only to render when its state or properties have changed, not every time the parent has changed. This is where component memoization comes in handy.
You can easily memoize a component by wrapping the component in memo(). You can either export a memoized version of an existing component, or you can make the component always memoized. Examples of both are below.
Heads up: memo() is different from useMemo(). useMemo() is for storing values between renders, not components.
This child component will render every time the parent's state changes:
// Parent component:
import { ChildComponent } from "./ChildComponent";
export const ParentComponent = ({ parentProp1 }) => {
console.log("ParentComponent last rendered at:", new Date().toLocaleTimeString());
return (
<div>
Amazing parent content with props {parentProp1}
<ChildComponent prop1="test 1" prop2="test 2" />
</div>
);
};
//Non-memoized child component:
export const ChildComponent = ({ prop1, prop2 }) => {
console.log("ChildComponent last rendered at:", new Date().toLocaleTimeString());
return (
Amazing child component content with props {prop1} and {prop2}
);
};
Option 1 - Export a separate memoized version of the component:
// Non-memoized child component with a memoized export:
import { memo } from "react";
export const ChildComponent = ({ prop1, prop2 }) => {
console.log("ChildComponent last rendered at:", new Date().toLocaleTimeString());
return (
Amazing child component content with props {prop1} and {prop2}
);
};
export const MemoizedChildComponent = memo(ChildComponent);
// Use the memoized version of the child component:
<MemoizedChildComponent prop1="test 1" prop2="test 2" />
// Use the original, non-memoized version of the child component:
<ChildComponent prop1="test 1" prop2="test 2" />
Your parent component can now reference either the memoized version using <MemoizedChildComponent />
, or the non-memoized version using <ChildComponent />
. This is useful
if you need both options.
Option 2 - Make the component always memoized:
// Memoized child component
import { memo } from "react";
export const ChildComponent = memo(function ChildComponent({ prop1, prop2 }) {
console.log("ChildComponent last rendered at:", new Date().toLocaleTimeString());
return (
Amazing child component content with props {prop1} and {prop2}
);
});
Your parent component will continue to reference the memoized version using <ChildComponent />
.
Callback Functions as Properties Break Memoization Without useCallback()
If you are passing a callback function as a property to the child, memoizing the component is not enough to avoided the extra renders. The callback function will appear by the child to be different every time the parent's state changes, which will trigger the child to re-render every time the parent re-renders. To fix this, you can memoize the function in the parent component using useCallback(), and pass the output of useCallback() as a property to the child. That will allow the memoized child component to render as expected: only when child properties (including callback functions) have actually changed.
// Memoized child component with callback function as parameter
import { memo } from "react";
export const ChildComponent = memo(function ChildComponent({prop1, onClickCallback}) {
console.log("ChildComponent last rendered at:", new Date().toLocaleTimeString());
return (
<div>
Amazing child component content with props {prop1} and a callback function
<button onClick={() => onClickCallback()}>click me to notify the parent</button>
</div>
);
});
// Revised parent component using useCallback() to memoize a callback function sent to the child
import { useCallback } from "react";
import { ChildComponent} from "./ChildComponent";
export const ParentComponent = ({ parentProp1 }) => {
console.log("ParentComponent last rendered at:", new Date().toLocaleTimeString());
const childComponentOnClick = useCallback(() => console.log("Child component click callback in parent."), []);
return (
<div>
Amazing parent content with props {parentProp1}
<ChildComponent prop1="test 1" onClickCallback={childComponentOnClick} />
</div>
);
};
Can you just memoize the child properties in the parent instead?
You might think that memoizing the properties for the child component in the parent component would possibly prevent the child from re-rendering. However, since a child component that accepts no properties will always render when the parent renders, passing down memoized properties to the child won't stop that behaviour. So no, memozing the properties in the parent before passing to the child won't stop the extra child renders.