How to Avoid Breaking Memoization with Reference Types in React
Table of Contents
Introduction
When To Use React.memo
How To Use Reference Types (Array, Object, and Function)With React.memo
Only Memoize Reference Types If They Don’t Change Frequently
Update The Reference Only When The Inner Value Changes
Use useCallback or useMemo To Memoize The Reference Type Prop
Conclusion
Introdction:
React.memo is a higher-order component that can improve the performance of your React application by avoiding unnecessary re-renderings. It does this by shallowly comparing the props of the component. If the props are the same, React reuses the memoized result and skips the next render.
However, this technique has a limitation: it does not work well for reference types. Reference types are values that are compared by reference, not by value. This means that if you pass an object or function as a prop, any parent component re-render will create a new reference, triggering React.memo to assume a prop change and causing unnecessary re-renders. This can lead to unexpected behavior if you’re not careful. For example, if you pass an object literal or a function declaration as a prop to a memoized component, you create a new object or function every time the parent component re-renders. This breaks the memoization and causes unnecessary re-renderings.
To avoid this problem, we’ll explore some strategies and best practices in this article to ensure consistent behavior in memoized components when dealing with reference types in React.
When To Use React.memo:
React.memo is not memoization in the traditional sense. It does not cache the result of a function based on its arguments. Instead, it works at looking at the previous value versus the new values of the props and then it re-renders if those have changed.
Therefore, you should use React.memo only when:
The component is expensive to render (e.g., it involves complex calculations or DOM manipulations).
The component renders often with the same props (e.g., it is part of a list or a table).
The component does not depend on any external state or props that are not passed explicitly (e.g., it does not use context or hooks).
If these conditions are not met, React.memo may not provide any significant performance benefit, or even worse, it may cause extra work with no benefit.
How To Use Reference Types With React.memo:
When using reference types (Array, Object, and Function) with React.memo, you need to ensure that the reference does not change unless the inner value changes. Otherwise, you may break memoization and cause unnecessary re-renderings.
There are three main ways to do this:
Only memoize reference types if they don’t change frequently.
Update the reference only when the inner value changes.
Use useCallback or useMemo to memoize the reference type prop.
Let’s look at each of these methods in more detail.
Only Memoize Reference Types If They Don’t Change Frequently:
The first strategy is to only memoize reference types if you don’t expect them to change frequently. For example, if you pass a constant object or array as a prop to a memoized component, there is no need to update the reference every time the component re-renders.
// A constant object
const user = {
name: "Alice",
age: 25,
};
// A memoized component
const UserCard = React.memo(({ user }) => {
return (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
</div>
);
});
// Passing the constant object as a prop
<UserCard user={user} />
In this example, the UserCard component will only render once, because the user prop is always the same reference.
Update The Reference Only When The Inner Value Changes:
The second strategy is to update the reference only when the inner value changes. For example, if you pass an object or an array that is derived from some state or props as a prop to a memoized component, you can use useState or useRef hooks to store the reference and update it only when necessary.
// A stateful object
const [user, setUser] = useState({
name: "Alice",
age: 25,
});
// A ref to store the reference
const userRef = useRef(user);
// Updating the ref only when the inner value changes
useEffect(() => {
userRef.current = user;
}, [user]);
// A memoized component
const UserCard = React.memo(({ user }) => {
return (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
</div>
);
});
// Passing the ref as a prop
<UserCard user={userRef.current} />
In this example, the UserCard component will only re-render when the user state changes, because the userRef.current prop will only change when the inner value changes.
Use useCallback or useMemo To Memoize The Reference Type Prop:
The third strategy is to use useCallback or useMemo hooks to memoize the reference type prop. useCallback and useMemo are hooks that return a memoized value based on some dependencies. useCallback returns a memoized function, while useMemo returns a memoized result of a function.
// A stateful value
const [count, setCount] = useState(0);
// A memoized function
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
// A memoized array
const numbers = useMemo(() => {
return [1, 2, 3, count];
}, [count]);
// A memoized component
const Counter = React.memo(({ increment, numbers }) => {
return (
<div>
<p>Count: {count}</p>
<p>Numbers: {numbers.join(", ")}</p>
<button onClick={increment}>Increment</button>
</div>
);
});
// Passing the memoized function and array as props
<Counter increment={increment} numbers={numbers} />
In this example, the Counter component will only re-render when the count or numbers props change, because they are memoized by useCallback and useMemo hooks.
Conclusion:
React.memo is a useful tool to optimize the performance of your React application by avoiding unnecessary re-renderings. However, it has a limitation: it does not work well for reference types. To avoid this problem, you can use one of the following strategies:
Remember that re-rendering in react is not a terrible thing react was built to manage that
Rename react memo in your brain to render if the props have changed
Only memoize reference types if you don’t expect them to change frequently.
Update the reference only when the inner value changes.
Use useCallback and useMemo because they are primarily around referential integrity and in particular around the integrity of arrays and objects and functions.
By using these strategies, you can ensure that your memoized components re-render only when they need to, and avoid breaking memoization with reference types.
Let's connect:
Subscribe to my newsletter
Read articles from Mohamed Zhioua directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Mohamed Zhioua
Mohamed Zhioua
Hеllo, I'm Mohamеd Zhioua, a dеdicatеd Full-Stack JavaScript Dеvеlopеr basеd in Tunis, Tunisia 📍. I'm on a mission to shapе thе futurе through codе.