What is the difference between useMemo and React useCallback?

Asked

Viewed 3,708 times

6

I know that useMemo and useCallback are two new Hooks React. However, I still don’t quite understand the difference between them.

What’s the difference between "return a memoised value" and "return a memoised callback"? The callback would not be a kind of value?

3 answers

8

The difference between the two is that the useCallback is not executed in the surrender while the useMemois.

This creates the possibility to "save" the function to be used only by callbacks for example, like clicking a button or inside a useEffect.

In the section below the useMemo will be executed on the first render, while the useCallback will only be executed if you run it, as in the useEffect that is commented or the click of button

const Component = () => {
  const testCallback = React.useCallback(() => {
    console.log('useCallback')
  }, [])

  const testMemo = React.useMemo(() => {
    console.log('useMemo')
  }, [])

  // testCallback só é executada manualmente
  // useEffect(() => {
  //  testCallback()
  // }, [])

  return (
    <div>
      <button onClick={() => testCallback()}>Executar testCallback</button>
    </div>
  )
}

ReactDOM.render(<Component />, document.querySelector('#app'))
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>

  • Important detail: The useMemo should not be used for the purpose of "performing the function during render" or anything like that. I describe the correct use in my answer. The useMemo is more related to the function return than to the function itself.

3

A good way to understand the difference between useMemo and useCallback is to see that it is possible to reproduce the useCallback using a useMemo.

Example:

const useCallback = useCallback(() => console.log('teste'), []);
const useCallbackUtilizandoUseMemo = useMemo(() => () => console.log('teste'), [])

useMemo runs the function initially and only runs again if the informed dependencies are changed, while useCallback does not execute the function, but returns a new reference to it when the dependencies change.

2


The only thing both Hooks have in common is that they return something memoizado. Although this is enough to confuse the functionality of both, throughout the answer it will be very clear what are the differences.

useCallback

const callbackMemoizado = useCallback(
  () => {
    // callback
  },
  [/* array de dependências */],
);

The useCallback returns a callback memoizado. What it means?

With each rendering of your component, all the code that is on it runs again. Therefore, functions are re-declared, and a new reference (in memory) is allocated to each function. The useCallback its function is redefined only when necessary, thus maintaining the same reference.

The dependency array is responsible for defining whether the callback will be reset or not. If there is a change in any of the dependencies, it will be reset. See the example below:

function App() {
  const [contador, setContador] = React.useState(0);

  const callbackAtualizado = React.useCallback(() => {
    console.log('callbackAtualizado:', contador);
  }, [contador]);
  
  const callbackNaoAtualizado = React.useCallback(() => {
    console.log('callbackNaoAtualizado:', contador);
  }, []);

  function incrementar() {
    setContador(contador + 1);
  }

  return (
    <div>
      <p>contador: {contador}</p>
      <button onClick={incrementar}>Incrementar</button>
      <button onClick={callbackAtualizado}>callbackAtualizado</button>
      <button onClick={callbackNaoAtualizado}>callbackNaoAtualizado</button>
    </div>
  );
}

ReactDOM.render(<App /> , document.querySelector("#app"));
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

<div id="app"></div>

In this example, so that the callbackAtualizado can display the new state value contador, it was necessary to create a new reference whenever this state was updated, since it is in the dependencies array.

Meanwhile, the reference of the callbackNaoAtualizado has never been modified, so it always displays the state of the first render, which is 0.

Note about the dependency array

The above example was done for the purpose of demonstration. You should avoid using variables (or functions) external to useCallback without putting them in the dependency array. If you are doing this, your application is probably having some logic problem.

There is a eslint plugin called eslint-plugin-react-hooks which has a rule for validating the dependency array. This rule would point to a problem in the callbackNaoAtualizado:

React Hook useCallback has a Missing dependency: 'counter'. Either include it or remove the dependency array.

In other words, "O React Hook useCallback has a dependency missing: 'counter'. Add or remove the dependency array.".

If we used an external function called funcao without adding to the dependencies array, the message would be:

React Hook useCallback has a Missing dependency: 'function'. Either include it or remove the dependency array. If 'funcao' changes Too often, find the Parent Component that defines it and wrap that Definition in useCallback.

"O React Hook useCallback has a dependency missing: 'function'. Add it or remove the dependency array. If 'function' changes too often, find the parent component that defines it and wrap this definition in a useCallback."

useMemo

const valorMemoizado = useMemo(
  () => {
    return; /* retorne algo a ser armazenado em `valorMemoizado` */
  },
  [/* array de dependências */],
);

The useMemo returns a value memoized. How this differs from the useCallback?

  1. The useMemo is executed during rendering. It is not a function to be called, but the return of the function passed to the useMemo.

  2. As the useCallback is used to maintain the passed function reference, the useMemo should be used to expensive calculations, and not to keep the reference of an object, for example.

The useMemo recalculate the memoized value only when one of the dependencies is modified, following the same reasoning explained for the useCallback.

Use the useMemo to optimize the performance of your application, not as a semantic guarantee. In the future, React may choose to "forget" some memoized value and recalculate it in the next rendering to, for example, free up memory of components that are not on screen at the moment.

Follow an example below:

function expensiveCalc(max) {
  const start = Date.now();
  let counter = 0;
  while (Date.now()-start < max) counter++;
  return counter;
}

function App() {
  const [contador, setContador] = React.useState(2000);
  const [horario, setHorario] = React.useState(new Date());
  
  React.useEffect(() => {
    // useEffect apenas para forçar uma re-renderização a cada segundo
    const id = setInterval(() => {
      setHorario(oldHorario =>
        new Date(oldHorario.getTime() + 1000)
      );
    }, 1000);
    return () => clearInterval(id);
  }, []);

  const calculoMemoizado = React.useMemo(() => {
    console.log(`Calculo realizado para`, contador);
    return expensiveCalc(contador);
  }, [contador]);

  const calculoNaoAtualizado = React.useMemo(() => {
    console.log(`Calculo nao atualizado realizado para`, contador);
    return expensiveCalc(contador);
  }, []);

  function incrementar() {
    setContador(contador + 1);
  }

  return (
    <div>
      <p>{horario.toLocaleString('pt-BR')}</p>
      <p>contador: {contador}</p>
      <p>calculoMemoizado: {calculoMemoizado}</p>
      <p>calculoNaoAtualizado: {calculoNaoAtualizado}</p>
      <button onClick={incrementar}>Incrementar</button>
    </div>
  );
}

ReactDOM.render(<App /> , document.querySelector("#app"));
<script crossorigin src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>

<div id="app"></div>

I added the current time with useEffect in the above example only to be more visible the freezing of the interface while the "expensive calculation" is being carried out, and also to show that the useMemo is not recalculated to any status update.

The function expensiveCalc does nothing else, just an expensive calculation for exemplification. I got it in that article.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.