20
Getting Started with React.memo()
There are is a number of built-in tools to improve performance of React applications. One of these tools is high-order component React.memo()
. This tutorial will help you learn about React.memo()
. You will learn what it is, how it works and how to use it in your React apps.
Let's start with what React.memo()
is. It is a high-order component (HOC). A higher-order component is a function that takes another component and returns a new component. You can think about HOC as a wrapper that transforms some component you give it into a new component.
React.memo()
helps us increase performance of React apps by avoiding unnecessary renderings of components. Every time React has to decide whether to update the DOM, it compares the previous render with the new render. If these two renders are different, some data are different, React will re-render the DOM to update it.
This is done in order to keep the UI in sync with the data. When this happens, React re-renders components that are currently rendered in the DOM. This can take some time and consume some resources, depending on how many components are rendered. The React.memo()
helps us make this process faster.
When we wrap some component with React.memo()
three things will happen. First, React will render the component on the initial render as usually. After that, however, React will also memoize the component. React will store the result of that render in memory.
Interesting thing happens when something causes React to re-render the DOM. This time, with memo()
, React will not automatically re-render the component. Instead, it will check if the new props of the component are the same as of the memoized component from the previous render.
If React recognizes that props of the component didn't change, it will reuse the memoized result of the previous render and skip re-rendering the component from scratch. React will basically re-use the "older" version of the component. When React uses the previous version of the component, it also re-uses whole content of that previous component.
This means that if we have some computation in that component this compilation may not repeat itself, unless it is necessary, i.e. some external resource changed. This means that we can avoid not only unnecessary re-renders of component but, more importantly, repeating computations that are not necessary inside those component.
What if the component props changed? React will re-render the component and run any necessary computations inside it. This, memoization with memo()
, works only with functional components. However, we can achieve this with class components if we use PureComponent.
One good thing on React.memo()
is that it is very easy to use. All we have to do is to take some functional component we want to memoize and wrap with memo()
. We can do this with new component we want to create as well as component that already exists.
// Functional component without memo():
export const App = () => {
return (
<div className="App">
<h1>Hello world</h1>
</div>
)
}
// Functional component with memo():
// Import memo from React:
import { memo } from 'react'
// Wrap App component with memo():
export const App = memo(() => {
return (
<div className="App">
<h1>Hello world</h1>
</div>
)
})
// Create component and memoize it later:
// Import memo from React:
import { memo } from 'react'
// Create component:
const App = () => {
return (
<div className="App">
<h1>Hello world</h1>
</div>
)
}
// Memoize and export App component:
export const AppMemoized = memo(App)
By default, React does shallow comparison of props object when it compares props from the previous render and the next. This will be enough in most cases, if the props you are passing are simple, i.e. primitive data types. It may not be enough if you are working with complex props.
For example, if you are passing objects or arrays through props, shallow comparison done by React will not be enough. React will probably fail to recognize that some object passed through props is the same as the previous. This is because when it comes to objects, React will compare references, not objects themselves.
This will cause problems with shallow comparison. Let's say we create a new object that is the same as some other object. The fact is that these two objects will not be the same. They will have the same shape, contain the same data, but they will have different references.
For React, and JavaScript as well, when it comes to objects, references are more important. When two references are different, shallow comparison will fail.
// Compare "the same" objects:
console.log({ foo: 'foo' } === { foo: 'foo' })
// Output:
// false
// Or:
const obj1 = { foo: 'foo' }
const obj2 = { foo: 'foo' }
console.log(obj1 === obj2)
// Output:
// false
// Compare "the same" arrays:
console.log([1] === [1])
// Output:
// false
// Or:
const arr1 = [1]
const arr2 = [1]
console.log(arr1 === arr2)
// Output:
// false
// Use the same reference:
const obj1 = { foo: 'foo' }
const obj2 = obj1
console.log(obj1 === obj2)
// Output:
// true
const arr1 = [1]
const arr2 = arr1
console.log(arr1 === arr2)
// Output:
// true
Fortunately, React allows us to use custom comparison function to check for props equality. So, if we know that we need more thorough comparison, we can provide React.memo()
with custom comparison function. This function comes as the second argument, right after the component we want to memoize.
// Functional component with memo():
// Import memo from React:
import { memo } from 'react'
import { isEqual } from 'lodash'
// Create custom comparison function:
function compareProps(prevProps, nextProps) {
return isEqual(prevProps, nextProps)
}
// Wrap with memo() and use custom comparison function:
export const App = memo(() => {
return (
<div className="App">
<h1>Hello world</h1>
</div>
)
}, compareProps) // Pass compareProps as the 2nd argument
// Create component and memoize it later:
// Import memo from React:
import { memo } from 'react'
import { isEqual } from 'lodash'
// Create component:
const App = () => {
return (
<div className="App">
<h1>Hello world</h1>
</div>
)
}
// Memoize with custom comparison function:
export const AppMemoized = memo(App, compareProps) // Pass compareProps as the 2nd argument
If you want to use custom comparison function, remember two important things. First, this function must always return a boolean. Second, it must return true
if previous props and next props are equal. Otherwise, it should return false
.
Everything has some price. This is why before you try to use memo()
by default you should consider one thing. When you use it, React stores the result of rendering component in memory. If you decide to memoize a large number of components it will lead to more memory consumption.
Another thing to consider is the comparison. When React compares previous and next props it requires some resources. This might not be such a big deal if your app is small or if you don't have too many memoized components. If you work on a bigger project, re-renders might be actually cheaper than memoization.
The fact is that React is already doing a good job at optimizing rendering performance. So, before trying to memoize everything, profile your app and identify issues. If you find that some components render when it is not necessary, try using React.memo()
. Then, profile your app again and compare the results.
Repeat this process with all components you want to try to memoize. If you see some meaningful improvements, keep the memoized version. Otherwise, if there is no improvement in performance or there is some penalty, just let the component re-render.
Aside to the above, there are some rules of thumb that can help you find components that might be worth memoizing. First, look for components that re-render often, usually with the same props. This often happens when component is forced to re-render by its parent component, even though the component itself didn't change.
React.memo()
can help you avoid these re-renders induced by parents. Second, try memoizing a component if the component always renders the same result given the same props. Third, your component renders a lot of UI. The more UI a component renders, the more expensive these renders usually are.
In these cases, resources necessary for memoization and comparison check might be smaller than resources for subsequent re-renders.
React.memo()
can be very useful tool when we want to improve performance of our React apps. It makes it very easy to memoize components and avoid unnecessary re-renders. I hope that this tutorial helped you understand what memo()
is, how it works and how to use it.
20