Skip to content

Rainbow
Apps

Rainbow
Apps

Prevent useless re-render

Before getting to the heart of the matter, I want to precise it's useless to focus on performances before getting real problems.

When does a component render?

It is important to know the conditions that makes a component renders. There is 4 reasons:

  • its props have changed (props passed by the parent)
  • its state changed
  • the parent renders
  • a context used by the component has its value that has changed

Among these reaons, 2 force a potential useless re-render of the component (because no changes of props, state and/or value used from a React context). Theses conditions are the following ones:

Subsequently we will focus to optimize when a children renders because of its parent.

The case of the context can be fixed by extracting the logic to get the wanted values from the context in a component that pass them to a component having the optimization that we are going to talk about. I think it's not necessarily the easiest way to go.

Another solution, I find more viable is to use the library use-context-selector. We will explore it in another article.

Class component

shouldComponentUpdate

Among all the lifecycle's method that you know componentDidMount, componentDidUpdate, componentWillUnMount, ... One allows to decide if a component needs to render or not. The name of this method is shouldComponentUpdate. It gets as parameters the next props and the next state. And it returns a boolean (true if the component has to render otherwise false).

shouldComponentUpdate(nextProps, nextState) {
    // Code
    // Returns a boolean if the component has to render or not
}

It's possible to compare the current props this.props with the potential next ones nextProps, and compares this.state with nextState.

For example if we have a component:

  • which displays a "box" (which can be stylized)
  • gets a message as prop to display in that box

If the message does not change, the re-render of the component is not needed, we will have to compare the message before/after to decide to render or not:

import React from 'react';

export default class Box extends React.Component {
    shouldComponentUpdate(nextProps) {
        // If the message is not the same that the next one
        // then the component has to render
        // otherwise no
        return this.props.message !== nextProps.message;
    }

    render() {
        const { message } = this.props;

        return (
            <div>
                {message}
            </div>
        )
    }
}

We can make comparison even more complicated. Or use deep equal when we have objects (if mutable);

We can also make the same thing on internal state of the component with the help of this.state and nextState (2nd parameter).

Potentially, we don't want a component to render if the props and state do not change. This is exactly the use case of PureComponent.

PureComponent

A PureComponent is a Component that implements by default the lifecycle method shouldComponentUpdate. Its implementation is really simple, it compares with shallow equals the props before/after as well as the state. Basically it is:

class MyComponent extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return !shallowEqual(this.props, nextProps) || shallowEqual(this.state, nextState);
    }
}

Warning: if a reference in the props or for an element of the state changes the method will return true and the component renders.

In a next part, we will see that it does not work if a component has a children which is not a primitive (example of primitive: number, string, boolean, ...). So, if we get a component as children, it's useless to have a PureComponent.

We can reimplement our component Box with:

import React from 'react';

export default class Box extends React.PureComponent {
    render() {
        const { message } = this.props;

        return (
            <div>
                {message}
            </div>
        )
    }
}

See codesandbox.

How is it coded in the React library?

To distinguish a Component from a PureComponent, a property isPureReactComponent is added to the prototype of the function PureComponent visible here.

Then we will have the following steps:

  1. During the ReactFiberBeginWork phase, to know if the component needs to render, the method resumeMountClassInstance is called.
  2. This one will call the method [checkShouldComponentUpdate](https://github.com/facebook/react/blob/f15f8f64bbc3e02911d1a112fa9bb8f7066a56ee/packages/react-reconciler/src/ReactFiberClassComponent.new.js#L1005
  3. If we are ine the case of a PureComponent then it returns the condition with shallow equals seen in the previous part.

We will see in a next article, what are the steps of the Fiber algorithm with detailed explanations.

Functional Component

Now, let's explore how to code the same thing than previously but with functional components. You just have to use React.memo.

The signature is:

function MyComponent(props) {}

function arePropsEqual(prevProps, nextProps) {
    // Send true if the component hasn't to
    // render because the nextProps will produce
    // the same result than with prevProps
    // Otherwise send false
}

// The method arePropsEqual is optional
React.memo(MyComponent, arePropsEqual);

The function arePropsEqual (2nd parameter) is completely optional. If we don't define a method then the props will be compared with shallow equal.

Warning: the condition is the opposite of shouldComponentUpdate.

Example: no custom equal function

In the case of our component Box we get:

import React from 'react';

function Box({ message }) {
    return (
        <div>
            {message}
        </div>
    )
}

export default React.memo(Box);

You can see this example in the codesandbox.

Example: with a custom equal function

If we have a component, which takes as parameter a list, and needs to render only when the size of the list changes (I know that it's a weird behavior but it's just for the example):

import React from 'react';

function MyList({ items }) {
    return (
        <ul>
            {items.map(item => (<li key={item.id}>{item.name}</li>))}
        </ul>
    )
}

export default React.memo(
        MyList, 
        (prevProps, nextProps) => prevProps.items.length === nextProps.items.length
    );

Children case problem

Description of the problem

We can notice that we have render problems, if we use PureComponent and React.memo (without a custom equal method), when we pass a children which is a component.

Thereafter, we will use functional components everywhere, but it's possible to apply the first solution to implement the shouldComponentUpdate when working with component class.

With the Box we get:

import React from 'react';

function Box({ children }) {
    return (
        <div>
            {children}
        </div>
    )
}

export default React.memo(Box);

And we use it this way:

import { useState } from 'react';
import MemoizedBox from './Box';

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount((prev) => prev + 1)}>
        Increment me: {count}
      </button>
      <MemoizedBox>
        <h1>The tittle of the Box</h1>
      </MemoizedBox>
    </div>
  );
}

See the codesandbox.

We can see that the Box render everytime even though its children doesn't seem to change (always the same title).

We can think that the solution is simple, we just have to make a component Title with our title which will be memoized.

import React from 'react';

function Title() {
  return <h1>The Title render</h1>;
}

export default React.memo(Title);

The usage will be:

import { useState } from "react";
import MemoizedBox from './Box';
import MemoizedTitle from './Title';

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount((prev) => prev + 1)}>
        Increment me: {count}
      </button>
      <MemoizedBox>
        <MemoizedTitle />
      </MemoizedBox>
    </div>
  );
}

When we test again, we realize that it doesn't work too. Whilde the component Title do not re-render, the Box renders.

So why does the component render even though the children doesn't?

Explanations

To understand the problem, it's important to know how works the transpilation of the code (with babel for example).

Actually our Title component, after transpilation, is transformed to the following code:

var React = require('react');

function Title() {
    return React.createElement('h1', null, 'The Title render');
}

You can check online transpilation with the site babeljs.io.

The signature of the method createElement is: React.createElement(type, [props], [...children])

Now we have to understand what does the method React.createElement return. This function is visible on github here.

We can see that it executes the method ReactElement and returns the result. The code of the method ReactElement is here.

ReactElement returns an object which have the next structure visible here:

const element = {
    // This allows us to identify this as a ReactElement
    // The constant is a symbol
    $$typeof: REACT_ELEMENT_TYPE,


    // Type of the element
    // For the title it's a h1
    type: type,
    // The potential key passed to the component
    key: key,
    // The potential reference of the component (prop ref)
    ref: ref,
    // Contains all the props, the children 
    // is merged to this
    props: props,


    // The component which has created the element
    // Type: Fiber 
    _owner: owner,
  };

The Fiber type is visible here.

We can imagine we have to use deep equality and not just shallow equality.

Let's test this quickly by using the deep equal of lodash. Link to the codesandbox.

It doesn't solve the renders problem. It comes from the _owner property of our ReactElement which contains circular dependencies.

If we use the 15.x.x version where Fiber was not used by React, the deep equal works well see codesandbox example. There is also an _owner but with a different implement which does not cause problems.

Solution 1: react-fast-compare

The particularity of the library react-fast-compare is that it does process the comparison of the key _owner: see here.

import reactFastCompare from 'react-fast-compare';

function Box({ children }) {
  console.log("The Box render");
  return <div>{children}</div>;
}

return React.memo(Box, reactFastCompare);

Used like this:

import { useState } from 'react';
import MemoizedBox from './Box';

export default function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount((prev) => prev + 1)}>
        Increment me: {count}
      </button>
      <MemoizedBox>
        <h1>The Title render</h1>
      </MemoizedBox>
    </div>
  );
}

With the help of the library, there is no more re-render of the Box and we do not need to make a component Title: see codesandbox.

We can also use the library fast-deep-equal because handles the case of _owner too here.

Solution 2: use useMemo

The second solution is to use useMemo to memoize the title and pass it as children to the Box component:

import { useState, useMemo } from 'react';
import MemoizedBox from './Box';

export default function App() {
  const [count, setCount] = useState(0);

  const renderTitle = useMemo(() => <h1>The Title render</h1>, []);

  return (
    <div>
      <button onClick={() => setCount((prev) => prev + 1)}>
        Increment me: {count}
      </button>
      <MemoizedBox>{renderTitle}</MemoizedBox>
    </div>
  );
}

And the Box code:

import React from 'react';

function Box({ children }) {
  return <div>{children}</div>;
}

export default React.memo(Box);

You can test this here.

Not to have changing props

We have just seen the solutions not to have useless re-render. But it is important not to have changing references when it is not needed, because most of the time we will use shallow equal for performances (no need to deeply compared objects).

Class components

Prototype methods

In this case defines classes methods, do not define them in the render method.

import React from 'react';
import MemoizedComponent from './MemoizedComponent';

class MyComponent extends React.Component {
    myMethod = () => {}

    render() {
        return <MemoizedComponent myMethod={myMethod} />
    }
}

The method is defined with fat arrow to have access the class instance with the value this, without the need to bind this manually.

Memoization of the data

If you need to process data to pass an object to the component, make a memoized method not to change the reference of the output if the input parameters do not change. You don't need of memoization if it returns a primitive.

import React from 'react';
import MemoizedComponent from './MemoizedComponent';
import memoize from 'memoize-one';

class MyComponent extends React.Component {
    getMyData = memoize((inputParameters) => {
        // Process data and return the output
        return output;
        }
    )

    render() {
        return <MemoizedComponent myData={this.getMyData(potentialParameters)} />
    }
}

If you want to know how works memoization in javascript you can read this article.

Functional components

Take out the method of the component

If you need to use a method in a component or to pass it to a component, it can be usefull to take the method out of the component if it does not need data of the component (props or state). This way the method will have a static reference.

For example, go from:

import MemoizedInputField from './InputField';

// An example of formular with a field which is valid uniquely
// when the value is Rainbow
function MyForm() {
    // The validation method is declared inside the component (in the render)
    // It does not use any data of the component MyForm
    // Its reference is changing each time the component renders
    // which makes the InputField renders even if memoized
    const validate = (name) => name === 'Rainbow';
    return (
        <MemoizedInputField label="name" validate={validate} />
    );
}

To:

import MemoizedInputField from './InputField';

// The method validate has been extracted
// Its reference does not change anaymore
const validate = (name) => name === 'Rainbow';

function MyForm() {
    return (
        <MemoizedInputField label="name" validate={validate} />
    );
}

In this case, if InputField is a memoized component, it will not render each time than MyForm renders, contrary to the firs snippet.

useCallback

When you have to pass a callback which needs data from the current component, it can be usefull to use the hook useCallback to keep a fixed reference as soon as the data does not change.

import MemoizedButton from './Button';

function IncrementButton({ setCount }) {
    // Each time the component renders
    // a new reference is created
    const incrementCount = () => setCount(prevCount => prevCount + 1);

    return (
        <MemoizedButton onClick={incrementCount}>
            Increment count
        </MemoizedButton>
    )
}

Each time IncrementButton render, the component Button will render too even if the component is memoized, because the callback incrementCount is recreated.

The solution is to use useCallback:

import { useCallback } from 'react';
import MemoizedButton from './Button';

function IncrementButton({ setCount }) {
    // The callback is only recreated when setCount changes
    const incrementCount = useCallback(
        () => setCount(prevCount => prevCount + 1),
        [setCount]
    );

    return (
        <MemoizedButton onClick={incrementCount}>
            Increment count
        </MemoizedButton>
    )
}

useMemo

It can be also usefull to use the hook useMemo, while we pass objects calculated in the render. This will stabilize the reference as soon as the dependencies do not change.

import MemoizedTable from './Table';

const COLUMNS = [
    {
        name: 'productName',
        label: 'Product name'
    },
    {
        name: 'productCategory',
        label: 'Category'
    },
];

function PageTable({ products }) {
    // A new reference is created 
    // each time the component renders
    // event if the products do not change
    const data = products.map(
        product => ({ 
            productName: product.name, 
            productCategory: product.category 
            })
    );

    return (
        <MemoizedTable columns={COLUMNS}
               data={data} />
    )
}

We can transform to:

import { useMemo } from 'react';
import MemoizedTable from './Table';

const COLUMNS = [
    {
        name: 'productName',
        label: 'Product name'
    },
    {
        name: 'productCategory',
        label: 'Category'
    },
];

function PageTable({ products }) {
    // We calculate the data only when the products change
    const data = useMemo(() => products.map(
        product => ({ 
            productName: product.name, 
            productCategory: product.category 
            })
    ), [products]);

    return (
        <MemoizedTable columns={COLUMNS}
               data={data} />
    )
}
01-21-2021

Rainbow Apps

Get it on Google Play

© Copyright 2021 Rainbow Apps. All rights reserved.

Rainbow Apps

© Copyright 2021 Rainbow Apps.
All rights reserved.

Get Numbersion app

Get it on Google Play

Follow me