An overview on react hooks useMemo and useCallback.

Hello everyone,

Hooks play a major role when we switch from class components to functional components in react. Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods. Here I have covered the three important and most commonly used hooks in react, useMemo, useCallback, and useEffect. I will explain each hook briefly and the minor differences between their usage.

For more information on hooks, please follow this link and read the react documentation on react-hooks:https://reactjs.org/docs/hooks-intro.html

Refer to the react documentation on various types of hooks and their usage: https://reactjs.org/docs/hooks-overview.html

useMemo

This hook works on the concept of memoization in react, which means caching in react. A create-function and an array of dependencies are passed as arguments to this hook. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render. So if we have complex functions in our program which have a long execution time, then it could become a drastic problem if we let them run on every render cycle. Refer to the code below for more clarity.

import {React,useState} from 'react';

export default function App() {
  const [number,setNumber] = useState(0);
  const [dark,setDark] = useState(false);

  const doubleNum = slowFunction(number);
  const theme = {
    background : dark ? 'black' : 'white',
    color : dark ? 'white' : 'black'
  } 

  return (
    <>
      <input type='number' value={number} 
      onChange={e => setNumber(e.target.value)}/>
      <button onClick={() => setDark(dark => !dark)}>
        Toggle</button>
      <div style={theme}>{doubleNum}</div>
    </>
  );
}

function slowFunction(num){
  for(var i = 0; i<=100000000; i++) {}
  return num*2;
}

https://codesandbox.io/s/slow-function-7zlcc?file=/src/App.js:0-636

  1. I have created a function named, slowFunction, which brings a delay of 1 sec between every render cycle.
  2. This slowFunction acts as a complex function that must render in every render cycle.
  3. The problem here is that whenever we enter a new number, the App component re-renders, and slowFunction is invoked.
  4. But if you observe, when we toggle the color theme, then also the theme change is delayed by 1 second which means any state change "number" or "dark" will re-render App component and invoke slowFunction in every cycle.
  5. To resolve this issue we must wrap the slowFunction in useMemo so that it only renders when the state of "number" changes. Check the code below and observe the difference.
import { React, useState,useMemo } from "react";

export default function App() {
  const [number, setNumber] = useState(0);
  const [dark, setDark] = useState(false);

  const doubleNum = useMemo( () => {
    return slowFunction(number);
  },[number])

  const theme = {
    background: dark ? "black" : "white",
    color: dark ? "white" : "black"
  };

  return (
    <>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />
      <button onClick={() => setDark((dark) => !dark)}>Toggle</button>
      <div style={theme}>{doubleNum}</div>
    </>
  );
}

function slowFunction(num) {
  for (var i = 0; i <= 100000000; i++) {}
  return num * 2;
}

https://codesandbox.io/s/usememo-slow-function-x69e7?file=/src/App.js

  1. I have wrapped the slowFunction in useMemo and provided a dependency over the state of number. What happens if the number is updated, then only 'slowFunction' is invoked and we observe a noticeable delay in the rendering of the result.
  2. But on toggling the theme we don't observe such delay, because even though state change on dark variable re-renders App component, slowFunction is not invoked.
  3. As it is wrapped under useMemo which makes sure until there is a change in a dependency, on every render cycle the cached value of the number is read and so slowFunction is not invoked, and hence no delay is seen.

Referential Equality using useMemo

In Javascript, objects are stored under references, so even though we are storing same value under different reference names, JS treats them as separate objects i.e. {} === {} is false. useMemo helps up achieve referential equality in case the need arises. Check out the below code for more understanding.

import { React, useState, useMemo, useEffect } from "react";

export default function App() {
  const [number, setNumber] = useState(0);
  const [dark, setDark] = useState(false);

  const doubleNum = useMemo(() => {
    return slowFunction(number);
  }, [number]);

  const theme = useMemo(() => {
    return {
      background: dark ? "black" : "white",
      color: dark ? "white" : "black"
    };
  }, [dark]);

  useEffect(() => {
    console.log("theme changed");
  }, [theme]);

  return (
    <>
      <input
        type="number"
        value={number}
        onChange={(e) => setNumber(e.target.value)}
      />
      <button onClick={() => setDark((dark) => !dark)}>Toggle</button>
      <div style={theme}>{doubleNum}</div>
    </>
  );
}

function slowFunction(num) {
  for (var i = 0; i <= 100000000; i++) {}
  return num * 2;
}

https://codesandbox.io/s/usememo-reference-compare-dr65f

  • So, what happens is whenever the theme is toggled a statement is printed on the console but that doesn't happen in case of the number change.
  • We are using useEffect to show this change. If the theme was not wrapped under useMemo, then on any state change i.e. dark or number we would observe console statement.
  • But since we have wrapped the theme under useMemo with dependency on dark, hence theme object is updated on toggling as a state change of dark re-renders App component and a new theme object is returned from useMemo and that theme is applied.
  • And since, theme object is updated we see the console statement because react performs referential equality comparison between new theme object and cached theme object, and even though the data is the same as the last render cycle, react treats it as a new object => theme is changed and useEffect is invoked.
  • To summarise,
    const cached theme =
    {
        background: dark ? "black" : "white",
        color: dark ? "white" : "black"
    }
    const updated theme =
    {
        background: dark ? "black" : "white",
        color: dark ? "white" : "black"
    }
    cached theme === updated theme => false;
    

useCallback

  • useCallback works nearly identically to useMemo since it will cache a result based on an array of dependencies, but useCallback is used specifically for caching functions instead of caching values.
  • This syntax may look the same as useMemo, but the main difference is that useMemo will call the function passed to it whenever its dependencies change and will return the value of that function call.
  • useCallback on the other hand will not call the function passed to it and instead will return a new version of the function passed to it whenever the dependencies change.
  • This means that as long as the dependencies do not change then useCallback will return the same function as before which maintains referential equality.
  • Check the code below which makes use of useCallback.
  1. In the code, getList is a function wrapped under inside useCallback. I have provided the number inside the array of dependency.
  2. useCalback, in this case, is returning, the function itself i.e.
    () => {
     return [number,number*2,number*3]
    }
    
  3. This function is passed as a prop to List and inside List, we have useEffect hook which gets triggered only when a new instance of prop getList is passed to it.
  4. Since we know that useCallback is triggered only when the number is updated so unless we provide a new input value the cached version of getList will be passed to List in every render cycle.
  5. And so on toggling, we see the state change of dark but useCallback will not be invoked as the state of number is not updated and hence cached version is passed to List.
  6. Inside List, useEffect will not trigger and we do not see console statement on toggling.
  7. You must uncomment the code below the useCallback definition and observe the difference.

If you want you can use useMemo with the same code. The only difference would be, that useMemo will return cache value rather than the function itself and so we have to call,

setItems(getList)

instead of

setItems(getList());

Conclusion

React has many ways built in to handle memoization for performance needs. This makes it incredibly easy to maintain highly performant applications without needing to write a bunch of additional code. It's up to the user to choose whichever hook they deem necessary as per their development requirement. Take reference to the code sandbox links which I have attached to the blog and keep experimenting in your way.