React: Optimize Components with React.memo, useMemo, and useCallback


Chris Held
React: Optimize Components with React.memo, useMemo, and useCallback

In most cases, React performance is not something you need to worry about. The core library does a ton of work under the hood to make sure everything is rendering efficiently. However, occasionally you can run into scenarios where your components are rendering more often than they need to and slowing your site down.

Let’s look at an example:

1
2
3
4
5
6
const ListPage = ({ data, title }) => (
  <div>
    <Header title={title} />
    <List listItems={data} />
  </div>
);

In this example, any changes to data will cause ListPage to re-render all of it’s child components, including the Header component, even if title didn’t change. The Header will render the same result given the same props, so any render with the same props is not necessary. In this case it’s probably not a big deal, but if <Header/> was performing some expensive computation every time it rendered we would want to make sure it was only rendering when necessary.

Thankfully, there are a few ways to optimize for this scenario.

When using class based components, PureComponent will return the last rendered value if the passed in props are the same. There is also a shouldComponentUpdate function for more fine tuned control. When using functional components, React provides three methods of optimization that this article will be focusing on: React.memo, useMemo, and useCallback.

Using hooks to improve performance

React.Memo

React.memo is a higher order component that memoizes the result of a function component. If a component returns the same result given the same props, wrapping it in memo can result in a performance boost. Take our <Header/> example earlier.

Let’s say it looks something like this:

1
2
3
const Header = ({ title }) => <h1>{title}</h1>;

export default Header;

Now, whenever Header is rendered, it will do a shallow comparison on it’s props. If those props are the same, it will skip rendering and instead return it’s last rendered value.

A quick note about using memo and react dev tools. At the time of this writing, wrapping your component like this…

1
2
3
const Header = React.memo(({title}) => <h1>{title}</h1>));

export default Header;

…will cause your component to show up as Unknown in react dev tools. To fix this, wrap your component in memo after defining it, like we did previously:

1
2
3
const Header = ({ title }) => <h1>{title}</h1>;

export default React.memo(Header);

useMemo

useMemo allows you to memoize the results of a function, and will return that result until an array of dependencies change.

For example:

1
2
3
4
5
6
7
8
9
const widgetList = useMemo(
  () =>
    widgets.map(w => ({
      ...w,
      totalPrice: someComplexFunction(w.price),
      estimatedDeliveryDate: someOtherComplexFunction(w.warehouseAddress)
    })),
  [widgets]
);

In this example, a component receives a list of widgets. The widgets coming into the component need to be mapped to include total price and an estimated delivery date, which uses some kind of complex and expensive function. If this component renders and the value of widgets is the same, there is no need to run those expensive functions again. Using useMemo will memoize the result, so if widgets haven’t changed since the component’s last render it will skip the function call and return what it got last.

useCallback

useCallback can prevent unnecessary renders between parent and child components.

Take this example:

1
2
3
4
5
6
7
8
const Parent = () => {
	const [showExtraDetails, setShowExtraDetails] = useState(false);
	return (
		[...]
		<Child onClick={() => { showData(showExtraDetails); }/>
		[...]
	);
}

This component will cause Child to re-render every time Parent does, even if Child is a PureComponent or wrapped in React.memo, because the onClick will be different every render. useCallback can handle this situation like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Parent = () => {
	const [showExtraDetails, setShowExtraDetails] = useState(false);
	const handleClick = useCallback(
	  () => {
	    showData(showExtraDetails);
	  },
	  [showExtraDetails],
	);
	return (
		[...]
		<Child onClick={handleClick}/>
		[...]
	);
}

Now handleClick will have the same value until showExtraDetails changes, which will reduce the number of times Child renders.

Things to consider with optimization in React

A word of caution around premature optimization. React is typically fast enough to handle most use cases without resorting to any of these techniques. I would advise you to build your components without any optimization first, and look into adding performance enhancements only when necessary.

Resources to learn more

If you’d like to explore these APIs further, here are some resources to help give you a better understanding.

React.memo

useMemo

useCallback
If you’re looking to further optimize your React application, the react docs contain a great section on performance.


SIGN UP FOR OUR NEWSLETTER

The Weekly Manifest

Receive the latest design, development, and startup articles to stay updated!

Close

Inquiry Sent!

Thanks so much for your interest in working with us, and for your time to fill out the form. We're passionate about what we do and would love the opportunity to create a successful solution for you.

Expect to hear back from us within the next 3 business days.

Work With Us

We can take on any type of project, but we don’t work with everyone. We only partner with clients that align with our business values, honestly benefit from our expertise, and embrace the systems we build in.

Fill out the form below to start a conversation see if you’re the right fit for us.

What type of project would you like to partner with us on?