In this article, I will provide a set of techniques to optimize child components render. There are many circumstances of unnecessary component re-rendering. Additional renders in child components usually happen when the state of the parent component changes.
Firstly we should note:
any manipulation with component
state
leads to re-rendering of the entire components tree, even if a child component doesn't have an interaction with the parent component state through props.
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
.
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.
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:
prevProps
and nextProps
).
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.
You can see the result below:
React
controls the second argument by default. This means comparing of theprops
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.
So that's all. It was a comprehensive React performance optimization tutorial.