A walkthrough on useMemo and useCallback

A walkthrough on useMemo and useCallback

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

This article will explore how re-rendering work optimization and performance in focus using useMemo and useCallback hooks. A basic understanding of React before starting this tutorial. You can learn more about React hooks from the official docs here.

useMemo() hook

useMemo stands for memorization which cases a value so that we don't recompile every time. It is important we don't memoize everything to avoid performance overhead because the side effect will that it calls every single re-render of components causing additional memory usage and performance problems.

useMemo is built-in Hooks introduced in 16.8 that accepts two arguments that render function with dependencies array.

user

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const calculateValue = useMemo(() => {
return expensiveCalc(users);
}, [users])

useMemo invokes users at the initial rendering and memoizes users at re-rendering for the component.

let's look at these examples and the difference when applied with uesMemo

import React, { useState } from "react";

const FirstComponent = () => {
    const [count, setCount] = useState(0);
    const [numbers, setNumbers] = useState(0);

    const isEven = () => {
        let i = 0;
        while (i < 3000000) i++
        return numbers % 2 === 0
    }

    return (
        <div>
            <div>
                <button
                    onClick={() => setCount(count + 1)}>
                    Counters - {count}
                </button>
            </div>
            <div>
                <button
                    onClick={() => setNumbers(numbers + 1)}>
                    Numbers - {numbers}
                </button>
                <span>{isEven() ? "Even" : "Odd"} </span>
            </div>
        </div>
    )
}
export default FirstComponent;

Every time we click on either button, there is a re-renders which recalculates the count and numbers functions. However, we don't need all buttons to re-render at every click but the one with heavy calculation.

`useMemo can help in handling this heavy calculation with better-optimized performance.

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

const FirstComponent = () => {
  const [count, setCount] = useState(0);
  const [numbers, setNumbers] = useState(0);

  const isEven = useMemo(() => {
    let i = 0;
    while (i < 3000000) i++;
    return numbers % 2 === 0;
  }, [numbers]);

  return (
    <div>
      <div>
        <button onClick={() => setCount(count + 1)}>Counters - {count}</button>
      </div>
      <div>
        <button onClick={() => setNumbers(numbers + 1)}>
          Numbers - {numbers}
        </button>
        <span>{isEven ? "Even" : "Odd"} </span>
      </div>
    </div>
  );
};
export default FirstComponent;

Every time we change the value of "counters" the value is displayed as expected.

However, if we click the "numbers" button "counters" does not re-render because number useMemo(() => { let i = 0; while (i < 3000000) i++; return numbers % 2 === 0; }, [numbers]) returns the memoized calculation.

useCallback() hook

useCallbackis a callback hook used when a child component is rerendering over and over again without any need.

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders.

Functions in JavaScript are first-class citizens, meaning that a function is a regular object.

Using JavaScript equality check below

true === true // true
false === false // true
3 === 3 // true
'c' === 'c' // true

{} === {} // false
[] === [] // false
() => {} === () => {} // false

const m = {}
m === m // true

The inline function works better when we run a function per component but when multiple functions are wrapped within components like "useEffect" or "useMemo", this is where implementation of useCallback can be effectively used.

import ParentCompoents from "./components/ParentComponent";

const countFunction = new Set();

const = App () => {
  const [count, setCount] = React.useState("");
  const [user, setUser] = React.useState([]);

  const handleUser(newUser) {
     setUser([newUser, ...user]);
  }

   countFunction.add(handleUser);
   console.log(countFunction)

   return (
   <>
      <ParentComponent user={user} handleUser={handleUser} />
      <button onClick={() => setCount(prev => prev + 1)}>{count} + </button>
      <button onClick={() => setCount(prev => prev - 1)}>{count} - </button>
    </>
   );
   }

Every time the state updates and our app re-renders the handleUser callback we pass down the component that is recreated. This is not something to worry about but If we are to handle multiple from Facebook, we might be updating our app quite a lot.

To solve this, we use a useCallback hook to handle the state efficiently.

import ParentCompoents from "./components/ParentComponent";

const countFunction = new Set();

const = App () => {
  const [count, setCount] = React.useState("");
  const [user, setUser] = React.useState([]);

  const handleUser = React.useCallback((newUser) {
     setUser([newUser, ...user]);
  }, [user] );

   countFunction.add(handleUser);
   console.log(countFunction)

   return (
   <>
      <ParentComponent user={user} handleUser={handleUser} />
      <button onClick={() => setCount(prev => prev + 1)}>{count} + </button>
      <button onClick={() => setCount(prev => prev - 1)}>{count} - </button>
    </>
   );
   }

Now can handle our data gracefully only when the state changes.

Conclusion

useMemo and useCallback is quite a complex topic and that may only be required on a large-scale project with huge computational calculations.

Here are references to Kent C. Dood and React Docs for further research.

Please share your thought and leave a like :)