DEV Community

Max Frolov
Max Frolov

Posted on

React performance optimization with useMemo & memo

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)

Alt Text

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:
Alt Text
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:

  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:
Alt Text

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)

Collapse
 
paulhoriachiy profile image
Paul Horiachiy

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!