8

How to Memoize with React.useMemo()

 3 years ago
source link: https://dmitripavlutin.com/react-usememo-hook/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

From time to time a React components can perform expensive calculations. For example, given a big list of employees and a search query, the component should filter the employees’ names by the query.

In such cases, with care, you can try to improve the performance of your components using the memoization technique.

In this post, I’m going to describe how and when to use the useMemo() React hook.

1. useMemo() hook

useMemo() is a built-in React hook that accepts 2 arguments — a function that computes a result and the depedencies array:

import { useMemo } from 'react';

function MyComponent() {
  const memoizedResult = useMemo(() => heavyCalc(a, b), [a, b]);

  // ...
}

During initial rendering, useMemo(() => heavyCalc(a, b), [a, b]) invokes heavyCalc(a, b), memoizes the calculation result, and returns it to the component.

If a and b dependencies don’t change during re-rendering, then useMemo() doesn’t invoke heavyCalc(a, b) but returns the memoized value.

However, if a and b change during re-rendering, then useMemo() invokes heavyCalc(a, b), memoizes the new value, and returns it.

That’s the essence of useMemo() hook.

Don’t forget to provide the dependencies argument. Otherwise, without dependencies argument, useMemo(() => heavyCalc(a, b)) invokes heavyCalc(a, b) during every re-rendering.

Now let’s see how useMemo() works in an example.

2. useMemo() — an example

A component <CalculateFactorial /> calculates the factorial of the number introduced by the user into an input field.

Here’s a possible implementation of <CalculateFactorial /> component:

import { useState } from 'react';

export function CalculateFactorial() {
  const [number, setNumber] = useState(1);
  const [inc, setInc] = useState(0);

  const factorial = factorialOf(number);
  const onChange = event => {
    setNumber(Number(event.target.value));
  };
  const onClick = () => setInc(i => i + 1);
  
  return (
    <div>
      Factorial of 
      <input type="number" value={number} onChange={onChange} />
      is {factorial}
      <button onClick={onClick}>Re-render</button>
    </div>
  );
}

function factorialOf(n) {
  console.log('factorialOf(n) called!');
  return n <= 0 ? 1 : n * factorialOf(n - 1);
}

Try the demo.

Every time you change the input value, the factorial calculation function is invoked factorialOf(n) and 'factorialOf(n) called!' is logged to console.

On the other side, each time you click Re-render button, inc state value is updated. Updating inc state value triggers <CalculateFactorial /> re-rendering. But, as a secondary effect, the factorial is recalculate too and 'factorialOf(n) called!' is logged to console each time you click Re-render.

How can you memoize the factorial calculation when the component re-renders? Welcome useMemo() hook!

By using useMemo(() => factorialOf(number), [number]) instead of simple factorialOf(number), React memoizes the factorial calculation.

Let’s improve <CalculateFactorial /> and memoize the factorial calculation:

import { useState, useMemo } from 'react';

export function CalculateFactorial() {
  const [number, setNumber] = useState(1);
  const [inc, setInc] = useState(0);

  const factorial = useMemo(() => factorialOf(number), [number]);
  const onChange = event => {
    setNumber(Number(event.target.value));
  };
  const onClick = () => setInc(i => i + 1);
  
  return (
    <div>
      Factorial of 
      <input type="number" value={number} onChange={onChange} />
      is {factorial}
      <button onClick={onClick}>Re-render</button>
    </div>
  );
}

function factorialOf(n) {
  console.log('factorialOf(n) called!');
  return n <= 0 ? 1 : n * factorialOf(n - 1);
}

Try the demo.

Open the demo. Every time you change the value of the number, 'factorialOf(n) called!' is logged to console. That’s expected.

However, if you click Re-render button, 'factorialOf(n) called!' isn’t logged to console because useMemo(() => factorialOf(number), [number]) returns the memoized factorial calculation. Great!

3. useMemo() vs useCallback()

useCallback(), compared to useMemo(), is a more specialized hook that memoizes callbacks:

import { useCallback } from 'react';

function MyComponent({ prop }) {
  const callback = () => {
    return 'Result';
  };
  const memoizedCallback = useCallback(callback, [prop]);  
  return <ChildComponent callback={memoizedCallback} />;
}

In the above example, useCallback(() => {...}, [prop]) returns the same function instance as long as prop dependency is the same.

You can use the same way the useMemo() to memoize callbacks:

import { useMemo } from 'react';

function MyComponent({ prop }) {
  const callback = () => {
    return 'Result';
  };
  const memoizedCallback = useMemo(() => callback, [prop]);  
  return <ChildComponent callback={memoizedCallback} />;
}

4. Use memoization with care

While useMemo() can improve the performance of the component, you have to make sure to profile the component with and without the hook. Only after that make the conclusion whether memoization worth it.

When memoization is used inappropriately, it could harm the performance.

5. Conclusion

useMemo(() => computation(a, b), [a, b]) is the hook that lets you memoize some heavy computations. Given the same [a, b] dependencies, once memoized, the hook is going to return the memoized value without invoking computation(a, b).

Also check the post Your Guide to React.useCallback() if you’d like to read about useCallback() hook.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK