Hooks: useMemo



React provides a core set of built-in hooks to simplify the development and built an efficient application. One of the important feature of an application is good performance. Every application will have certain complex computation logic, which will slow down the application performance. We have two option to encounter the complex computation.

  • Improve the algorithm of the complex calculation and make it fast.

  • Call complex calculation only when it is absolutely necessary.

The useMemo hook helps us to improve the performance of the application by providing an option to call the complex calculation only when it is absolutely necessary. useMemo preserves / memoize the output of the complex calculation and returns it instead of recomputing the complex calculation. useMemo will know when to use memoized value and when to rerun the complex calculation.

Signature of useMemo hook

The signature of the useMemo hook is as follows −

const <output of the expensive logic> = useMemo(<expensive_fn>, <dependency_array>);

Here, useMemo accepts two input and returns a computed value.

The input parameter are as follows −

  • expensive_fn − Do the expensive logic and returns the output to be memoized.

  • dependency_array − Hold variables, upon which the expensive logic depends. If the value of the array changes, then the expensive logic has to be rerun and the value have to be updated.

The output of the useMemo hook is the output of the expensive_fn. useMemo will return the output either by executing the function or fetching the memorized value.

Usage of useMemo hook is as follows −

const limit = 1000;
const val = useMemo(() => {
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
   return sum;
}, [limit])

Here, summation logic will execute once in the beginning and whenever the limit value is changed/updated. In between, the memoized value is returned by the useMemo hook.

Applying memo hook

Let us learn the useMemo hook deeply by applying the hook in a react application.

First of all, create and start a react application by using create-react-app command as shown below −

create-react-app myapp
cd myapp
npm start

Next, create a component, Sum under components folder (src/components/Sum.js)

import React from "react";
function Sum() {
   return <div>Sum</div>
}
export default Sum

Next, update the root component with out Sum component as shown below −

import './App.css';
import Sum from './components/Sum';
function App() {
   return (
      <div>
         <Sum />
      </div>
   );
}
export default App;

Next, open Sum.js and add a state to represent the current time and update the current time through setInterval as shown below −

import React, {
   useState,
   useEffect,
   useMemo
} from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const limit = 1000;
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
   return (
      <div style={ {padding: "5px" } }>
         <div>Summation of values from <i>1</i> to <i>{limit}</i>:
            <b>{sum}</b></div>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   )
}
export default Sum

Here we have,

  • Used setState to set the current time in the state using useState hook. It returns two variables; A state variable, currentTime and a function setCurrentTime to update the state variable, currentTime.

  • Used useEffect hooks to update the time at regular interval of 1 second using setInterval function. We have updated the time at regular interval to make sure that the component rerenders at regular interval.

  • Used clearInterval to remove the update time functionality when the component is unmounted.

  • Shown sum and currentTime value in the document through JSX

  • Sum component rerenders every second to update the currentTime. During each render, summation logic will execute even through it is not necessary.

Next, let us introduce the useMemo hook to prevent recalculation of the summation logic between the rerender as shown below −

import React, { useState, useEffect, useMemo } from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const limit = 1000;
   const sum = useMemo(() => {
      var sum = 0;
      for(let i = 1; i <= limit; i++) {
         sum += i;
      }
      return sum;
   }, [limit])
   return (
      <div style={ {padding: "5px" } }>
         <div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   )
}
export default Sum

Here we have, converted the summation logic into a function and passed it into useMemo hook. This will prevent the recalculation of the summation logic on every rerender.

The limit is set as dependency. This will ensure that the summation logic will not be executed until the limit value is changed.

Next, add a input to change the limit as shown below −

import React, {
   useState,
   useEffect,
   useMemo
} from "react";
function Sum() {
   let [currentTime, setCurrentTime] = useState(new Date())
   let [limit, setLimit] = useState(10);
   useEffect(() => {
      let interval = setInterval(() => setCurrentTime(new Date()), 1000)
      return () => { clearInterval(interval) }
   })
   const sum = useMemo(() => {
   var sum = 0;
   for(let i = 1; i <= limit; i++) {
      sum += i;
   }
      return sum;
   }, [limit])
   return (<div style={ {padding: "5px" } }>
   <div><input value={limit} onChange={ (e) => { setLimit(e.target.value) }} /></div>
      <div>Summation of values from <i>1</i> to <i>{limit}</i>: <b>{sum}</b></div>
      <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
   </div>)
}
export default Sum

Here we have,

  • Used useState hook to manage limit variable. useState hook will return two variables; a state variable, limit and a function variable, setLimit to update limit variable.

  • Added a new input element to allow the user to enter new value for the limit variable

  • Added an event, onChange to update the limit variable as the user updates it.

When user enters new value in the input element,

  • onChange event will get triggered.

  • setLimit function will be called by the onChange event.

  • limit variable will be updated by setLimit function.

  • Sum component will be rerendered as the state variable, limit is updated.

  • useMemo will rerun the logic as the limit variable is updated and it will memoize the new value and set it to sum variable.

  • render function will use the new value for sum variable and render it.

Finally, open the browser and check the application.

Memo Hook

Preserving references

Let us understand how to use useMemo hook along with reference data type in this chapter. In general, data type of a variable comes under category, value types and reference types. Value types are managed in stack and they are passed between function as values. But, reference data types are managed in heap and are passed between function using its memory address or simply references.

The Value types are as follows −

  • Number

  • String

  • Boolean

  • null

  • undefined

Reference data types are as follows −

  • Array

  • Objects

  • Functions

In general, variables with reference data types are not easily comparable even through the value of two variable are identical. For example, let us consider two variable with value type and another two variable with reference data type and compare it as shown below −

var a = 10
var b = 10
a == b // true
var x = [10, 20]
var y = [10, 20]
x == y // false

Even though x and y are same with respect to value, both points to different location in memory. useMemo can be used to memoize variables with reference data type correctly.

useMemo(x) == useMemo(y) // true

Let us create a new react application to better understand the usage of useMemo to handle reference data types.

First of all, create and start a react application by using create-react-app command as shown below −

create-react-app myapp
cd myapp
npm start

Next, create a component, PureSumComponent under components folder (src/components/PureSumComponent.js)

import React from "react";
function PureSumComponent() {
   return <div>Sum</div>
}
export default PureSumComponent

Next, update the root component with PureSumComponent component as shown below −

import './App.css';
import PureSumComponent from './components/PureSumComponent';
function App() {
   return (
      <div style={{ padding: "5px" }}>
         <PureSumComponent />
      </div>
   );
}
export default App;

Next, open PureSumComponent.js and add a props, range to represent the range of number as shown below −

import React from "react";
function PureSumComponent(props) {
   const range = props['range'];
   const start = range[0];
   const end = range[1];
   console.log('I am inside PureSumComponent component')
   var sum = 0;
   for(let i = start; i <= end; i++) {
      sum += i;
   }
   return (
      <div>
         <div>Summation of values from <i>{start}</i> to
            <i>{end}</i>: <b>{sum}</b></div>
      </div>
   )
}
export default React.memo(PureSumComponent)

Here,

  • Used range props to calculate the summation of the range of values. range is an array having two numbers, start and end of the range.

  • Shown start, end and sum value in the document through JSX

  • Wrapped the component using React.memo to memoize the component. Since the component will render same output for the given set of input, it is called PureComponent in react.

Next, let us update App.js and use the PureSumComponent as shown below −

import React, {useState, useEffect} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
   var input = [1,1000]
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ {padding: "5px" } }>
         <PureSumComponent range={input}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

Here, we have included a state, currentTime and updated it every second using setInterval to make sure the component rerenders every second.

We may thought that PureSumComponent will not rerender every second as it uses React.memo. But, it will rerender as the props is an array and it will be created with new reference with every second. You can confirm it by checking the console.

Next, update the root component and use useMemo to preserve the range array as shown below −

import React, {useState, useEffect, useMemo} from 'react';
import PureSumComponent from './components/PureSumComponent';
function App() {
   const input = useMemo(() => [1, 1000], [])
   const [currentTime, setCurrentTime] = useState(new Date())
   useEffect(() => {
      let interval = setInterval(() => {
         setCurrentTime(new Date())
      }, 1000)
      return () => clearInterval(interval)
   }, [currentTime])
   return (
      <div style={ {padding: "5px" } }>
         <PureSumComponent range={input}/>
         <div>Time: <b>{currentTime.toLocaleString().split(',')[1]}</b></div>
      </div>
   );
}
export default App;

Here, we have used useMemo to preserve the range array

Finally, check the application in the browser and it will not rerender PureSumComponent every second.

Advantages

Advantages of useMemo hook are as follows −

  • Simple to use API

  • Easy to understand

  • Improves the performance of the application

Disadvantages

Technically, useMemo have no disadvantages. But, heavy usage of useMemo in a application will bring below disadvantages.

  • Decrease the readability of the application

  • Decrease the Understandability of the application

  • Debugging the application is complex

  • Developer should have deep understanding of JavaScript language to use it

Summary

In general, react internally optimize the application and provides high performance. Still, we may need to intervene and provide our own optimization in certain scenarios. useMemo helps us in this situation and provides a simple solution to improve the performance of the react application. To summarize, use useMemo hook when it is absolutely necessary. Otherwise, leave the optimizing and providing the high performance to react itself.

Advertisements