In this article, I will provide a set of techniques to optimize child components re-rendering. There are many circumstances of unnecessary component re-rendering. Usually, it happens because of the parent component inside which the state changes.
Firstly we should note:
any manipulation with component state leads to re-rendering of entire components tree in spite of the interaction with parent component state.
In case your app is small, without heavy components - additional re-rendering is bearable. It doesn't affect app performance so much. The bigger app and individual components inside it - the more noticeable effects of unnecessary re-rendering are. It leads to processes delay and increasing of loads on all components.
Here is the example of such re-rendering. To track the re-rendering I left console.log
in the render of each internal component. The number of the re-rendered element will be displayed in the console.
---FormContainer
------ItemComponent1 (console.log)
---------ItemComponent2 (console.log)
There are several options to solve this problem:
№1 - useMemo
This hook is mainly designed to optimize calculations. The calculation restarts if the dependencies specified as a second argument changed. Thus, the load on the component reduce.
useMemo
is also applicable to components, returning them persisted. It works if the dependencies do not change during the component 's lifecycle. In case we don't specify dependencies (leave an empty array) - the component remains, as it was at the time of initialization. All the passed parameters remain closed in the initial state.
import React from 'react'
// local variables
const FIELD_NAMES = {
FIRST_NAME: 'firstName',
LAST_NAME: 'lastName'
}
const FormContainer = () => {
const [formValues, changeFormValues] = React.useState({
[FIELD_NAMES.FIRST_NAME]: '',
[FIELD_NAMES.LAST_NAME]: ''
})
const handleInputChange = fieldName => e => {
const fieldValue = e.target.value
changeFormValues(prevState => ({
...prevState,
[fieldName]: fieldValue
}))
}
return (
<div>
<input
type='text'
onChange={handleInputChange(FIELD_NAMES.FIRST_NAME)}
name={FIELD_NAMES.FIRST_NAME}
value={formValues[FIELD_NAMES.FIRST_NAME]}
/>
<input
type='text'
onChange={handleInputChange(FIELD_NAMES.LAST_NAME)}
name={FIELD_NAMES.LAST_NAME}
value={formValues[FIELD_NAMES.LAST_NAME]}
/>
<ItemComponent1 />
</div>
)
}
const ItemComponent1 = () => {
console.log('ITEM 1 RENDERED')
return React.useMemo(
() => (
<div>
<span>Item 1 component</span>
<ItemComponent2 />
</div>
),
[]
)
}
const ItemComponent2 = () => {
console.log('ITEM 2 RENDERED')
return <div>Item 2 component</div>
}
In the example above, we used useMemo
inside ItemComponent1
. Thus anything returns the component will be initialized only once. It won't be re-rendered at the time of parent re-rendering.
Below you can see the result of how hook works:
As you see, when the state changes inside the FormContainer
, the useMemo
does not allow component ItemComponent1
to re-render.
One more thing. Let's assume we specified firstName
as a dependency passed via props from the parent. In this case, the component will be re-rendered only if firstName
value changes.
№2 - memo
You can reach the same effect using a high order component (HOC
) named memo
. If you don’t want the component ItemComponent2
involved in re-rendering - wrap it in memo
. Here we go:
const ItemComponent2 = React.memo(() => {
console.log('ITEM 2 RENDERED')
return <div>Item 2 component</div>
})
If we pass props to a component wrapped in a HOC memo
, we will be able to control the re-rendering of that component when the prop changes. To do this we should pass as a second argument a function which:
- Compares the props values before and after the change (
prevProps
andnextProps
) - Returns a boolean value upon which React will understand whether to re-rendered the component or no.
const ItemComponent1 = ({ firstNameValue, lastNameValue }) => {
console.log('ITEM 1 RENDERED')
return (
<div>
<span>Item 1 component</span>
<ItemComponent2
firstNameValue={firstNameValue}
lastNameValue={lastNameValue}
/>
</div>
)
}
const ItemComponent2 = React.memo(
() => {
console.log('ITEM 2 RENDERED')
return <div>Item 2 component</div>
},
(prevProps, nextProps) =>
prevProps.firstNameValue !== nextProps.firstNameValue
)
In this example above we compare old firstName
and new props state. If they are equal the component will not be re-rendered. Hereby we ignore the lastName
on which change the firstName
will have the same value all the time. That is why the component will not be re-rendered.
React controls the second argument by default. This means comparing of the props value only to primitive types and one nested level. Props as
objects, functions, or arrays
will cause component re-render. You can control such moments describing needed comparison in the function. The function should passed as a second argument.
Another hooks tutorials:
useState
useReducer
More tips and best practices on my twitter.
Feedback is appreciated. Cheers!
Top comments (1)
From other point of view adding useMemo/useCallback is more expensive than re-render [kentcdodds.com/blog/usememo-and-us...]. But yes, if that operation is reasonable it should be used. Thanks!