React performance optimization with useMemo & memo.

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.


---FormContainer
------ItemComponent1 (console.log)
---------ItemComponent2 (console.log)

There are several options to solve this problem:

№1 - hook 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 ItemComponent1to 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 - HOC 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:

  1. Compares the props values before and after the change (prevProps and nextProps).
  2. 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.

You can see the result below:

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.

So that's all. It was a comprehensive React performance optimization tutorial.