How to implement Memoization in React and vanilla JS to optimize your web app

How to implement Memoization in React and vanilla JS to optimize your web app

Before we get into how to implement memorization let's first see what it is and why we even need it.

What is Memoization?

  • In simple words, Memoization is an optimization technique where we ask our computer to remember in its cache what the output of a function call is for specific input and when the function is called with the same input instead of doing the function execution and re-calculating the value, the corresponding cached value is returned.
  • This is particularly useful when the function is expensive to calculate and in the case of React it helps to avoid unnecessary re-renders more on this in the later parts.

Memoization in JS:

A simple JS implementation for Memoization would be as follows :

Without Memoization:

Code :

const expensiveFunction = (num) => {
  console.log("recalculation done");
  for (let i = 0; i < 100000; i++) { }
  return num + 2;
}

expensiveFunction(2)   // prints recalculation done
expensiveFunction(2)   // prints recalculation done

Output :

recon.PNG

With Memoization:

Code:

const cache = {};
const expensiveFunction = (num) => {

  const givenArg = JSON.stringify(num)
  if (cache[givenArg]) {
    return cache[givenArg]
  }
  else {
    console.log("recalculation done");
    for (let i = 0; i < 100000; i++) { }
    cache[num] = num + 2
    return num + 2;
  }
};

expensiveFunction(2) // prints recalculation done
expensiveFunction(2) // does not print anything since else part is not executed

Output: image.png

Memoization in React

  • We have seen how memoization helps when there are expensive calculations to be made
  • In the case of React the expensive operation with which memorization helps the most is the re-rendering of components.
  • First we have to know that React components re-render whenever there is a change in their state or props
  • Additionally , when the parent re-renders then so does the child component irrespective of whether its props or state were changed and when this happens in a fairly large project then performance suffers to tackle this issue we use memorization and it can be achieved using three APIs:

     -  React.memo()
     -  useMemo()
     -  useCallback()
    
  • One important thing to note is that in React we can memoize only the most recent input's corresponding output value unlike what we have seen with JS where all the values were stored in cache, there are several reasons for this but React this strategy does achieve memoization in React now let's see how that happens

Using React.memo() for Memoization:

React.memo() is a higher order component (HOC) that returns a new component only if the props of the component with which we have called it are changed. This way the child component will no longer re-render even if the parents component does.

Let us look at an example to understand this better:

Without using React.memo()

Code:

// ParentComponent.jsx

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

const ParentComponent = () => {
  const [counter, setCounter] = useState(0);
  const name = "poornima";
  const clickHandler = () => {
    setCounter((prev) => prev + 1);
  };
  return (
    <>
      <p>{counter}</p>
      <button onClick={clickHandler}>counter</button>
      <ChildComponent propVal={name} />
    </>
  );
};

export default ParentComponent;
// ChildComponents.jsx
export const ChildComponent = ({ propVal }) => {
    console.log("child component rendered", propVal);
    return <></>;
};

Output:

counter.PNG counternomemo.PNG As mentioned the child component has been re-rendered along with the parent component even though the props passed to it have not changed.

Using React.memo()

Code:

//  ParentComponent.jsx
import { useState } from "react";
import MemoChildComponent from "./ChildComponent";

const ParentComponent = () => {
  const [counter, setCounter] = useState(0);
  const name = "poornima";
  const clickHandler = () => {
    setCounter((prev) => prev + 1);
  };
  return (
    <>
      <p>{counter}</p>
      <button onClick={clickHandler}>counter</button>
      <MemoChildComponent propVal={name} />
    </>
  );
};

export default ParentComponent;
// Modified Child component
import React from "react";

const ChildComponent = ({ propVal }) => {
  console.log("child component rendered", propVal);
  return <></>;
};

const MemoChildComponent = React.memo(ChildComponent);
export default MemoChildComponent;

Output: counter.PNG usememo2.PNG Code Sandbox to play with the code

Yayyy! The child component is not re-rendered, it is rendered only the first time, we have achieved memoization in React.

Now we can use memo() in all cases right? Welll.....

React.memo() drawback :

  • If we pass a function or an array or object as a prop in the ChildComponent above then it will be re-rendered again. For example let us change the line :
 <MemoChildComponent propVal={name} />

to

<MemoChildComponent propVal={someFunc} />

Then the child component will get re-rendered just like how it happened when we didn't use React.memo() Similarly, if we pass some array say const users=["John Doe", "Jane Doe"]

<MemoChildComponent propVal={users} />

We will once again get a re-renders

  • This is because the function is recreated every time on render so it will be considered as a new value similarly for array and object since the memory location values change we get a new value every time.
  • Hence as prop values have changed re-rendering occurs, but worry not useCallback() and useMemo() are used to precisely tackle these drawbacks.

Using useCallback() for memoization:

useCallback() is a hook and it takes two arguments - a callback function and a dependency array, the function is recreated only when the dependency array changes Now let us try to use this in the previous example:

Using useCallback():

The ChildComponent remains the same let's make some changes to the ParentComponent where we create memoized function and pass it as prop:

// ParentComponent.jsx
import { useCallback, useState } from "react";
import MemoChildComponent from "./ChildComponent";

const ParentComponent = () => {
  const [counter, setCounter] = useState(0);
  const clickHandler = () => {
    setCounter((prev) => prev + 1);
  };

  const add2 = () => {
    return 1 + 1;
  };
  const propFunc = useCallback(() => add2(), []);
  return (
    <>
      <p>{counter}</p>
      <button onClick={clickHandler}>counter</button>
      <MemoChildComponent propVal={propFunc} />
    </>
  );
};

export default ParentComponent;

Output:

memoUsecb.PNG newusbmemo.PNG Code Sandbox to play with the code

Voila!! This time the component is not re-rendered , because we have given an empty array as dependency which means the function is created only on the first render and the value of propFunc never changes.

Using useMemo() for memoization:

Just like useCallback() , useMemo() has two arguments a callback function and a dependency array, and the function is recreated when the dependency array value changes, the only difference is that is that useCallback() memoizes a function whereas useMemo() memoizes the value returned by the function. Let us look at the code example to understand it better:

Using useMemo():

The ChildComponent remains the same let's make some changes to the ParentComponent where we create memoized array value by using useMemo() and pass it as a prop:

import { useState, useMemo } from "react";
import MemoChildComponent from "./ChildComponent";

const ParentComponent = () => {
  const [counter, setCounter] = useState(0);
  const clickHandler = () => {
    setCounter((prev) => prev + 1);
  };

  const users = useMemo(() => ["John Doe", "Jane Doe"], []);
  return (
    <>
      <p>{counter}</p>
      <button onClick={clickHandler}>counter</button>
      <MemoChildComponent propVal={users} />
    </>
  );
};

export default ParentComponent;

Output: memouseMemo.PNG useMemoop.PNG Code Sandbox to play with the code

Yep you guessed it right re-rendering doesn't happen because the users array was memoized!

Congratulations if you have made it this far!✨🥳 You can now use memoization to make that web app perform smooth af.