loading...
Headway

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

chrisheld927 profile image Chris Held ・4 min read

This article was originally posted on the Headway blog. Visit us at headway.io to see how we're making waves. πŸ„β€β™€οΈ

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:

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.

‍

two men discussing react project on computer screen

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:

const Header = ({title}) => <h1>{title}</h1>

export default Header;  

‍

We can see that this component isn't going to need to be rendered unless title changes, so it would be safe to wrap it in React.memo.

‍

const Header = ({title}) => <h1>{title}</h1>

export default React.memo(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...

‍

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:

‍

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:

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:

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:

‍

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.

Discussion

pic
Editor guide
Collapse
hirusi profile image
Ru Singh

Any idea why React.memo causes Unknown in devtools when passing the function definition itself rather than the exported variable?

Collapse
chrisheld927 profile image
Chris Held Author

Good question! It's because the function that gets passed into React.memo is anonymous, so dev tools doesn't know what to call it even though the component itself is called Header.

Collapse
hirusi profile image
Ru Singh

Oh that makes perfect sense!

Collapse
mmm profile image
sasasasasmmm

nice explanation ! keep up the good work

Collapse
rammyblog profile image
Onasanya Tunde

Great article