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
.
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.
If you're looking to further optimize your React application, the react docs contain a great section on performance.
Top comments (3)
nice explanation ! keep up the good work
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 calledHeader
.Great article