React Memo

Optimize React Performance with React Memo and Save $$$ by Caching

June 30, 2022
Temitope Asama
React.js

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Introduction to React Memo

Functions are an essential part of programming. They contribute to the modularity and reusability of our code.

It’s rather typical to split our software into smaller chunks by employing functions that we may later call to execute some helpful logic.

It is often costly to invoke a function several times especially recursive and mathematical functions in a React app. However, there is a technique to optimize such operations and make them run significantly faster: caching.

Caching

In programming, a cache is a hardware or software component that saves data to serve data more quickly in case of future requests; the data stored in a cache may be the result of an earlier calculation or a duplicate of data stored elsewhere.

What then is memoization? 

What is Memoization?

Memoization is a type of caching when the return result of a function is cached based on the function parameters.

According to Wikipedia

  • Although related to caching, memoization refers to a specific case of this optimization, distinguishing it from forms of caching such as buffering or page replacement

In other words, future calls with remembered inputs return the stored result rather than recalculating it, removing the major cost of a call with specified parameters from all except the initial call.

  • A function can only be memoized if it is a pure function, which means that invoking the said function has the same effect as replacing the function call with its return value.

JavaScript objects are great candidates for acting as caches since they behave like associative arrays, i.e. abstract data types that hold data in key-value pairs.

In summary, whenever our memoized function is called, one of two things might happen:

  • If the input has already been utilized, locate it in the cache and instantly return the stored value without further computations.
  • Use the input to perform required computations, cache the results, and return the result.

Memoization in JavaScript: A simple illustration

Let’s create a simple memoizing function in Javascript to illustrate all we’ve gone over so far:

We begin defining our memoizeFn()as an ES6 arrow function. As a parameter, it accepts a function we will call func for our operations.

const memoizeFn = (func) => {}

Next, we declare a Javascript object to serve as our cache.

const memoizeFn = (func) => {
    let cache = {}
}

Now we will first implement the first possibility.

Case #1: If the input has been used before, find its corresponding value1 in the cache and return it with executing any further operation.

const memoizeFn = (func) => {
    let cache = {};
    return (input) => {
      if (input in cache){
         return cache[input]
      }
}

Right now, our cache is still empty and let’s implement the second condition i.e. Case #2  to fix that. 

Case #2: If input is not in our cache object, use it to execute what computation exists further, store its results in our cache and return its result.

const memoizeFn = (func) => {
    let cache = {};
    return (input) => {
      if (input in cache){
         return cache[input]
      } else {
         let result = func(input);
         cache[input] = result;
         return result;
      }
}

Perfect! Now we have a memoizing function that accepts an input as a parameter. If the input is stored in our cache, our function returns its value with executing further operations, else, it uses the input to perform the necessary computation, stores the result and returns the result to us.

Memoization in React

Memoization in React differs from the conventional method of memoization in plain Javascript. In React, memoization controls the re-rendering of an entire component based on a change in props or state. This is useful because it prevents unnecessary renders and performance costs. 

How React Memoizes

Three APIs in React: React.memo(), useMemo, and useCallback handles memoization. The caching technique used by React has a size of 1. That is, they just preserve the most recent input and outcome values. This choice was made for a variety of reasons, but it addresses the major use case for memoizing in a React environment.

Memoizing in a React environment will look something like this in plain Javascript:

let prevValue, prevResult
function multiplyByTen(input) {
  if (input !== prevValue) {
    prevResult = input * 10
  }
  prevValue = input
  return prevResult
}

In this case,

MultiplyByTen(3) // 30 is computed
MultiplyByTen(3) // 30 is returned from the "cache"
MultiplyByTen(2) // 20 is computed
MultiplyByTen(3) // 30 is freshly computed and not returned from the cache.

In summary, React verifies the equality of each prop and dependency separately and uses that as a basis for re-rendering a component.

Similarities between memoization in JavaScript and memoization in React

  • Both methods are performance optimization techniques.
  • Both methods solve the problem of minimizing expensive function calls by caching return values.

Differences between memoization in JavaScript and memoization in React

  • In React, memoization controls re-rendering of a component based on change in props and state, while in plain Javascript, memoization has no effect on the DOM or rendering of elements.
  • In Javascript, the “cache” is capable of storing multiple previous results, regardless of when the operation was carried out and result was stored. However, in React, the caching technique used has a size of 1. That is, they just preserve the most recent input and output values.
  • React has 3 APIs for memoization, There’s no dedicated API for memoization in Javascript.

Implementing Memoization in Class-Based Components in React

React.PureComponent()

React utilizes the React.PureComponent component to implement memoization in class-based components. 

Pure Components

Based on the idea of purity in functional programming paradigms, a function is considered to be pure if and only if the following conditions are met:

  • It always returns the same value for the same input values.
  • Its return value is decided only by its input values.

A React component is also deemed pure if it returns the same output for the same state and props. Hence a classic example of both a classic functional pure React component would look like;

//Pure Functional Component
// Component returns the same first name and last name as its input. Nothing is altered.
export default function PureComponent({firstname, lastName}) {
  return (
    <div>My name is {firstName} {lastName}</div>
  )
}

A class-based pure React component would look like;

// Pure Class-based Component

class PureComponent extends React.Component {
    render() {
      return (
        <div>My "name is {this.props.firstname} {this.props.lastName}</div>
      )
    }
  }

export default PureComponent

Class-based React components that extend the React.PureComponent class have additional performance efficiency and render optimizations. This is due to React’s implementation of the shouldComponentUpdate() function, which carries out a shallow comparison for props and state, then decides whether to update and re-render the component or not.

React.PureComponent(): A simple Illustration

Let’s see an example. First, we’ll create a Parent counter component with a count increment variable, where a button controls its state. The parent component calls to a child component inside of it, passing a prop to it. Now we can add console.log() statements in the render method of both the classes.

The Parent Component should look like this;

// ParentComponent.js (Without memoization)

import React from "react"

class ParentComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = () => {
    this.setState((prevState) => {
      return { count: prevState.count + 1 };
    });
  };

  render() {
    console.log("The parent component is rendered");
    return (
      <div className="App">
        <button onClick={this.handleClick}>Increment</button>
        <h2>{this.state.count}</h2>
        <ChildComponent name={"Kyle"} />
      </div>
    );
  }
}

export default ParentComponent;

The Child component should a pure component in this format;

// ChildComponent.js

import React from "react"

class ChildComponent extends React.Component {
  render() {
    console.log("The child component is rendered");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
}

export default ChildComponent;

The count value changes whenever we click the button in the parental component. The render method of the parental component’s render method invokes because there is a state change.

Because the props supplied to the child class are the same for each parent re-render, the children component should not re-render. However, when we run the preceding code and continue to increase the count, we get the following result:

React Memo examples

From the code above, we can see that even though it is always getting the same prop, the child component is re-rendering.

To apply memoization and optimize the type of situation, we must extend the React.PureComponent class in our child component, as seen below:

//By extending React.PureComponent instead of React.Component, we force a shallow comparison of the prop in the Child component and utilize the shouldComponentUpdate() method to prevent re-rendering.

import React from "react"

class ChildComponent extends React.PureComponent {
    render() {
      console.log("The child component is rendered")
      return (
          <h2>{this.props.name}</h2>
      )
    }
  }

export default ChildComponent;

By doing this, now when we click on the increment button, only the parental component renders and we are said to have memoized the child component. So our app renders like this.

Check out the code for the example above in this Codesandbox.

Memoization in Functional Components in React with React.memo()

In React v16, React introduced a higher-order component React.memo() to increase user interface speed in functional React components. When React.memo() wraps a component, React memoizes the wrapped component’s displayed output and avoids redundant renderings.

What is React.memo()

React v16 introduced React.memo(), a higher order function, to memoize functional React components. In other words, when you wrap a component in React.memo(), React renders that component and memoizes the result. If the new props are the same as the previous ones, React reuses the memoized result and skips the next render. Additionally, React.memo() will shallowly compare complex objects in props object.

How to use React.memo()

If we transform our previous example to a functional component, we will have the following;

// ParentComponent.js

import { useState } from 'react'
import ChildComponent from "./ChildComponent"

export default function ParentComponent() {

    const [count, setCount] = useState(0)

    return (
        <div className="App">
            {console.log("The parent component is rendered")}
            <button onClick={() => setCount(count-1)}>Increment</button>

            <h2>{count}</h2>

            <ChildComponent name={"Kyle"} />
        </div>                    
    )
}

In the Child Component,

import React from 'react'

export default function ChildComponent({name}) {
console.log("The child component is rendered")
  return (
    <div>{name}</div>
  )

This code provoke the same problem of the unnecessary re-rendering of the child component. So to resolve this using React.memo(), we can do the following;

First, import memo from React.

import { memo } from "react"

And finally wrap our child component with React.memo().

import { memo } from "react"

export default React.memo(function ChildComponent({name}) {
console.log("The child component is rendered")
  return (
    <div>{name}</div>
  ))

As such, on button click, the child component is memoized and only the parental component is re-rendered. React.memo() is similar in functionality to React.PureComponent.

Watch this video to learn more about React memo.

Custom Prop Equality: areEqual()

By default, React.memo() performs a shallow comparison of props and prop objects.

However, you can use the second argument to specify an equality check function to customize the props comparison:

function areEqual(prevProps, currentProps) {
  return (prevProps === currentProps) ? prevProps : currentProps;
}

We can then pass this function as a second argument in React.memo().

React.memo(MyComponent, areEqual(prevProps, currentProps));

The areEqual(prevProps, currentProps) function must return true if prevProps and currentProps are equal.

When to use React.Memo()

  • With pure functional components: All your components with the same props always produce the same output.
  • When we anticipate a functional component to render after the first render, generally with the same props: This can happen if a parent component forces a child component to render. In such circumstances, you should use memoization on your React components from the same props.
  • When our component(s) are vast in size and include a significant number of UI components, re-rendering would result in a noticeable response latency for the user.

When not to use React.Memo()

  • Where performance benefit is negligible: The first and most apparent reason we should avoid using React.memo() is when we cannot quantify the speed advantages. If the computational time of re-rendering our component — with and without this higher-order component — is small or non-existent, then utilizing React.memo() is pointless.
  • With class-based components: While it is feasible to encapsulate class components in React.memo(), it is recognized (and rightfully so) as bad practice and is strongly discouraged. Instead, when working with class components, extending a React.PureComponent class is more ideal and a much clearer manner of managing memoization.

To learn more about React best practices, check out this article.

// ChildComponent.js

import React from "react"
import { memo } from "react"

//NEVER DO THIS!
export default React.memo(class ChildComponent extends React.Component {
  render() {
    console.log("The child component is rendered");
    return (
      <div>
        <h2>{this.props.name}</h2>
      </div>
    );
  }
})

export default ChildComponent;
  • Needless props comparison: Consider a component that typically renders with several props. In this situation, memoization is ineffective.
  • Even if we encapsulate a volatile component with React.memo(), React does two tasks on each rendering:
  • Invokes the comparison method to see if the previous and next props are the same.
  • Because comparing props nearly always returns false, React compares past and current render results.
  • We get no performance gains by doing this while also rendering the comparison feature useless.

Common Pitfalls of Using React.memo()

Referential Equality: Dealing with Objects, Arrays, Sets etc.

So far, we have seen the uses of React.memo() in prop comparison and memoization. However, React.memo() only shines with value prop comparison. In the case of value props, a shallow comparison does the trick of comparing previous results to current ones rather precisely, but with referential props, the shallow prop comparison falls short and hence React.memo() ceases to work.

To learn more about value and referential comparison, check out the video below:

This type of situation calls for custom prop comparison. Luckily, React introduces another API to solve this problem. This API is the useMemo() API.

useMemo()

You may wonder why referential equality is significant. Referential equality plays an important role when it comes to dependency arrays, for example when using useEffect.

Consider this example.

function MyComponent({ param1, param2 }) {
  const params = { param1, param2, param3: 27}

  useEffect(() => {
    console.log(params)
  }, [param1, param2])
}

Here, we want to update MyComponent and log the params object to the console whenever param1 or param2 changes. The dependency array in this case would be [param1, param2]. 

At first glance, this useEffect appears to work properly, but because the params object is created as a new object each render, the effect will actually run every render because the reference to params changes each render. However, React useMemo can help with this.

useMemo works similarly to useEffect in that it accepts a dependency array as its second argument. On every update made to the component, React useMemo carries out a prop comparison of these dependencies. Therefore, as long as param1 and param2 do not change, the params object remains cached and the component fails to re-render.

Learn more about useMemo() on this video. You can also check out this article on React memo vs useMemo.

Referential Equality: Dealing with Function Props

Let’s consider a Parent example with a Child component that contains a button. 

export default function Parent() {
  const handleClick = {
        // handle click event
  }
  return <MyChild onClick={handleClick} />;
}

React.memo(function Child ({ onClick }) {
  return <button onClick={onClick}>I am a child</button>;
})

In the case, Parent passes a handleClick function as a prop to a memoized Child where the handleClick function is referenced via the onClick handler of a button within the Child component.

Normally, we would expect that the memoized child does not re-render as long as the function prop remains constant. However, the opposite is the case.

useCallback()

Before we address our example above, let us discuss why React.memo() fails to memoize in the presence of function props. 

Functions are first-class citizens in Javascript. This basically means a function is a regular object. This fact brings us to the referential equality we discussed with useMemo. With regular objects. React.memo() fails because {} === {} returns false every time.

Consider the following function that returns another function that adds two numbers.

function getSum() {
  return (x, y) => x + y;
}
const sum1 = getSum();
const sum2 = getSum();

sum1(2, 3); // 3
sum2(2, 3); // 3

sum1 === sum2 // false
sum1 === sum1 // true
sum2 === sum2 // true

Although, sum1 and sum2 two share the same function declaration, they are two different references and function objects, hence sum1 === sum2 evaluates to false.

How is this significant?

Because a new function object is created on every re-render, this means the previous function object is always different from the current function object and as a prop, every change in our function object triggers a re-render.

But we do not want this. We need a way to cache the function prop to prevent unnecessary renders. This is where useCallback shines.

Given the same dependencies, useCallback gives us the same function object on every re-render.

The consequence of this on our earlier example as seen below is that onClick, the Child component is not forced to re-render.

import { useCallback } from 'react';
export default function Parent() {
    const handleClick = useCallback(() => { // handleClick is cached
          // handle click event
    }, []);
  return <MyChild onClick={handleClick} />;
}

React.memo(function Child ({ onClick }) {
  return <button onClick={onClick}>I am a child</button>;
})

It is important to note that it is not advisable to use useCallback hook in small cases like above. It costs us more performance optimization that it offers us. If anything, we should only use useCallback hook in situations where we have expensive function props and larger React components.

Watch this video to learn more about useCallback()

Why do we need Memoization in React?

Like mentioned in the React docs,

  • …This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs…

Memoization is a great way to speed up heavy and larger application by optimizing their rendering performance. It’s also great as a performance boost. Using React.memo() for any other situation outside this might introduce bugs to your code and is therefore discouraged.

Conclusion

React is already excellent at optimizing performance. Most of the time, you shouldn’t bother optimizing unnecessary re-renders. Always begin by measuring and identifying performance problems. It’s a good idea to assess your React app ahead of time to see which components render the most frequently. The most impact will be achieved by applying React.memo() to those components. As a result, you should see a performance boost.

CopyCat also helps optimize your React app development process by speeding up the development process. Convert Figma to React automatically with the click of a button and save time writing tedious boilerplate code. You’ll get to focus on the challenges you want and build faster than your competition.

Interesting Reads From Our Blog

Related Articles

  • React.js

    What to Read When Using React Typescript In Your App

    Introduction These days, businesses are migrating their applications and digital product from plain ol' Javascript to Typescript. Why? Well, we could liken Typescript to Javascript with superpowers. How? We'll find out as we progress. Before we go into details, check…

    October 1, 2022
  • React.js

    Using Material UI Autocomplete to Implement an Autocomplete Feature in React

    Do you want to be more efficient? Consider CopyCat, an AI-powered plugin that will convert your Figma design to UI code, allowing you to concentrate on the app's business logic. Convert Design to Code with CopyCat Introduction According to Wikipedia:…

    December 15, 2022

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code