What does useCallback/useMemo do in React?

JavascriptReactjsReact Hooks

Javascript Problem Overview


As said in docs, useCallback Returns a memoized callback.

Pass an inline callback and an array of inputs. useCallback will return a memoized version of the callback that only changes if one of the inputs has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

But how does it work and where is the best to use it in React?

P.S. I think visualisation with codepen example will help everyone to understand it better. Explained in docs.

Javascript Solutions


Solution 1 - Javascript

This is best used when you want to prevent unnecessary re-renders for better performance.

Compare these two ways of passing callbacks to child components taken from React Docs:

1. Arrow Function in Render
class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <Button onClick={() => this.handleClick()}>Click Me</Button>;
  }
}
2. Bind in Constructor (ES2015)
class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <Button onClick={this.handleClick}>Click Me</Button>;
  }
}

Assuming <Button> is implemented as a PureComponent, the first way will cause <Button> to re-render every time <Foo> re-renders because a new function is created in every render() call. In the second way, the handleClick method is only created once in <Foo>'s constructor and reused across renders.

If we translate both approaches to functional components using hooks, these are the equivalents (sort of):

1. Arrow Function in Render -> Un-memoized callback
function Foo() {
  const handleClick = () => {
    console.log('Click happened');
  }
  return <Button onClick={handleClick}>Click Me</Button>;
}
2. Bind in Constructor (ES2015) -> Memoized callbacks
function Foo() {
  const memoizedHandleClick = useCallback(
    () => console.log('Click happened'), [],
  ); // Tells React to memoize regardless of arguments.
  return <Button onClick={memoizedHandleClick}>Click Me</Button>;
}

The first way creates callbacks on every call of the functional component but in the second way, React memoizes the callback function for you and the callback is not created multiple times.

Hence in the first case if Button is implemented using React.memo it will always re render (unless you have some custom comparison function) because the onClick prop is different each time, in the second case, it won't.

In most cases, it's fine to do the first way. As the React docs state:

> Is it OK to use arrow functions in render methods? Generally speaking, > yes, it is OK, and it is often the easiest way to pass parameters to > callback functions. > > If you do have performance issues, by all means, optimize!

Solution 2 - Javascript

useCallback and useMemo are an attempt to bypass weak spots that come with the functional programming approach chosen with React hooks. In Javascript, each entity, no matter if it is a function, variable, or whatever, is created into the memory when the execution will enter the function's code block. This is a big issue for a React that will try to detect if the component needs to be rendered. The need for rerendering is deducted based on input props and contexts. Let's see a simple example without useCallback.

const Component = () => {
  const [counter, setCounter] = useState(0);

  const handleClick = () => {
    setCounter(counter + 1);
  }

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleClick}>+1</button>
  </div>
}

Note that the handleClick -function instance will be created on each function call inside the block, so the event handler's address on each call will be different. The React framework will always see the event handler as changed because of this. In the example above, React will think handleClick as a new value on each call. It simply has no tools to identify it as the same call.

What useCallback does, it internally stores the first introduced version of the function and returns it to the caller, if the listed variables have not changed.

const Component = () => {
  const [counter, setCounter] = useState(0);

  const handleClick = useCallback(() => {
    setCounter(counter + 1);
  }, [])

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleClick}>+1</button>
  </div>
}

Now, with the code above, React will identify the handleClick -event handler as the same, thanks to useCallback -function call. It will always return the same instance of function and React component rendering mechanism will be happy.

Storing the function internally by the useCallback will end up with a new problem. The stored instance of the function call will not have direct access to the variables of the current function call. Instead, it will see variables introduced in the initial closure call where the stored function was created. So the call will not work for updated variables. Thats why you need need tell if some used variables have changed. So that the useCallback will store the current function call instance as a new stored instance. The list of variables as the second argument of the useCallback is listing variables for this functionality. In our example, we need to tell to useCallback -function that we need to have a fresh version of counter -variable on each call. If we will not do that, the counter value after the call will be always 1, which comes from the original value 0 plus 1.

const Component = () => {
  const [counter, setCounter] = useState(0);

  const handleClick = useCallback(() => {
    setCounter(counter + 1);
  }, [counter])

  return <div>
    Counter:{counter}<br/>
    <button onClick={handleClick}>+1</button>
  </div>
}

Now we have a working version of the code that will not rerender on every call.

It is good to notice that the useState -call is here just for the same reason. Function block does not have an internal state, so hooks are using useState, useCallback and useMemo to mimic the basic functionality of classes. In this sense, functional programming is a big step back in history closer to procedural programming.

useMemo is the same kind of mechanism as useCallback but for other objects and variables. With it, you can limit the need for component rerender, as the useMemo -function will return the same values on each function call if the listed fields have not changed.

This part of the new React hooks -approach is definitely the weakest spot of the system. useCallback is pretty much counterintuitive and really error-prone. With useCallback-calls and dependencies, it is too easy to end up chasing internal loops. This caveat we did not have with the React Class approach.

The original approach with classes was more efficient after all. The useCallback will reduce the need to rerender, but it regenerates the function again every time when some of its dependant variables will change, and matching if the variables have changes itself will make overhead. This may cause more rerenders than necessary. This is not the case with React classes.

Solution 3 - Javascript

I've made a small example to help others understand better how it behaves. You can run the demo here or read the code bellow:

import React, { useState, useCallback, useMemo } from 'react';
import { render } from 'react-dom';

const App = () => {
    const [state, changeState] = useState({});
    const memoizedValue = useMemo(() => Math.random(), []);
    const memoizedCallback = useCallback(() => console.log(memoizedValue), []);
    const unMemoizedCallback = () => console.log(memoizedValue);
    const {prevMemoizedCallback, prevUnMemoizedCallback} = state;
    return (
      <>
        <p>Memoized value: {memoizedValue}</p>
        <p>New update {Math.random()}</p>
        <p>is prevMemoizedCallback === to memoizedCallback: { String(prevMemoizedCallback === memoizedCallback)}</p>
        <p>is prevUnMemoizedCallback === to unMemoizedCallback: { String(prevUnMemoizedCallback === unMemoizedCallback) }</p>
        <p><button onClick={memoizedCallback}>memoizedCallback</button></p>
        <p><button onClick={unMemoizedCallback}>unMemoizedCallback</button></p>
        <p><button onClick={() => changeState({ prevMemoizedCallback: memoizedCallback, prevUnMemoizedCallback: unMemoizedCallback })}>update State</button></p>
      </>
    );
};

render(<App />, document.getElementById('root'));

Solution 4 - Javascript

An event handler gets recreated and assigned a different address on every render by default, resulting in a changed ‘props’ object. Below, button 2 is not repeatedly rendered as the ‘props’ object has not changed. Notice how the entire Example() function runs till completion on every render.

const MyButton = React.memo(props=>{
   console.log('firing from '+props.id);
   return (<button onClick={props.eh}>{props.id}</button>);
});

function Example(){
   const [a,setA] = React.useState(0);
   const unmemoizedCallback = () => {};
   const memoizedCallback = React.useCallback(()=>{},[]);   // don’t forget []!
   setTimeout(()=>{setA(a=>(a+1));},3000);
   return (<React.Fragment>
                 <MyButton id="1" eh={unmemoizedCallback}/>
                 <MyButton id="2" eh={memoizedCallback}/>
                 <MyButton id="3" eh={()=>memoizedCallback}/>
           </React.Fragment>);
} 
ReactDOM.render(<Example/>,document.querySelector("div"));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionZiiMakcView Question on Stackoverflow
Solution 1 - JavascriptYangshun TayView Answer on Stackoverflow
Solution 2 - JavascriptVille VenäläinenView Answer on Stackoverflow
Solution 3 - JavascriptstackoverflowView Answer on Stackoverflow
Solution 4 - JavascriptChong Lip PhangView Answer on Stackoverflow