Today I learned how we can optimize our component re-render process even more efficiently using primitive values through props.
To recap, our ListItem component used to look like this:
function ListItem({
getItemProps,
item,
index,
selectedItem,
highlightedIndex,
...props
}) {
const isSelected = selectedItem?.id === item.id
const isHighlighted = highlightedIndex === index
return (
<li
{...getItemProps({
index,
item,
style: {
fontWeight: isSelected ? 'bold' : 'normal',
backgroundColor: isHighlighted ? 'lightgray' : 'inherit',
},
...props,
})}
/>
)
}
Here, what we are doing is simply taking the selectedItem & highlightedIndex props and calculating the isHighlighted & isSelected values inside the component, which can become troublesome, especially when we want to optimize the re-renders by comparing that particular prop’s state. Hence, we had to do a workaround as we did above to ensure if we really need to re-render or not.
But this kind of workaround is difficult to maintain in larger codebases, where there are 100s of components with many childrens nested into each other.
Therefore we need a better approach to solve this. The simplest way to achieve this, is to pass Primitive values through props instead of fields that later needs to calculated inside the component.
For e.g. instead of passing the selectedItem & highlightedIndex, we will simply pass the isHighlighted & isSelected values from the parent or may be parent’s parent component.
Let’s see this in action:
function ListItem({
getItemProps,
item,
index,
isSelected,
isHighlighted,
...props
}) {
// The below two lines are no longer useful, since instead of calculating them in the component,
// we will take this value from the props directly.
// const isSelected = selectedItem?.id === item.id
// const isHighlighted = highlightedIndex === index
return (
<li
{...getItemProps({
index,
item,
style: {
fontWeight: isSelected ? 'bold' : 'normal',
backgroundColor: isHighlighted ? 'lightgray' : 'inherit',
},
...props,
})}
/>
)
}
ListItem = React.memo(ListItem)
Now, we just need to update the parent component like this, where instead of passing the highlightedIndex we are passing whether the component is highlighted or not, and the same for selectedIndex as well, we are passing whether the concerned component is currently selected or not:
function Menu({
items,
getMenuProps,
getItemProps,
highlightedIndex,
selectedItem,
}) {
return (
<ul {...getMenuProps()}>
{items.map((item, index) => (
<ListItem
key={item.id}
getItemProps={getItemProps}
item={item}
index={index}
isSelected={selectedItem?.id === item.id}
isHighlighted={ highlightedIndex === index}
>
{item.name}
</ListItem>
))}
</ul>
)
}
Menu = React.memo(Menu)