What does useCallback/useMemo do in React?
Asked Answered
O

5

84

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.

Overton answered 5/11, 2018 at 17:27 Comment(0)
B
148

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!

Biddle answered 6/11, 2018 at 18:25 Comment(7)
Then if my handleClick function have some data fetching or parametres changes then i should NOT use useCallback, yes?Overton
Yes, if your return value could change even with the same parameters, then you shouldn't use useCallback because the return value is memoized.Biddle
How do you use it with double arrow function? Like this: const tagLike = useCallback((tagId, tagIndex) => async () => {}) ?Overton
@YangshunTay keep in mind that if you do pass arguments into the useCallback function and add the [] in the end(to not make it track changes), it will always return the memoized function and not update the arguments.Newmarket
The examples need to include one where the event object is used.Kumkumagai
For people who don't notice the second argument and may be curious why it doesn't work, it needs [] as the second argument of useCallback in order to tell React to memoize regardless of arguments.Edieedification
@YangshunTay, why you said if handleClick contains asynchronous call, then we shouldn't use useCallback? By your complete description that is even better than the Docs, we use useCallback when we wanna pass it to an optimized child component as props. right? So why we shouldn't use useCallback?Over
G
23

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.

Goodsell answered 11/4, 2021 at 9:36 Comment(6)
This is a great answer!Hamza
Amazing. You should start a blog or sthBesot
Great answer. I really felt the pain of this recently when being unnecessarily tasked with migrating a React Native codebase away from React classes.Retort
According to this answer, the function is still recreated each time the component is re-rendered.Hesperidium
@ospider, naturally function is created on each call. That is the very problem, why this work-around is needed. Those React's use-functions make it so, that the old one is being used instead of the newly created. This way React will avoid unnecessary re-rendering.Corselet
A clear, concise and bold answer! Thanks @VilleVenäläinenSynoptic
S
8

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'));
Senskell answered 8/4, 2019 at 19:16 Comment(2)
I'm not sure this is useful, memoizedCallback is invalid as it accesses memoizedValue, and has an empty deps list, no?Kumkumagai
@Kumkumagai when you update the state by pressing "update state" button, you should see that the prevMemoizedCallback is equal with the next memoizedCallback, which does what it's supposed to do, to memoize a callback that can be used across updates. The fact that in this example I've used a memoizedValue it's just for demonstrating that you can make use of the first random value generated in the initial render and for other updates, new random values are generated each time.Senskell
D
0

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"));
Delk answered 17/8, 2021 at 8:36 Comment(0)
Y
0

useCallback → if there are multiple components rendered within the parent component and the event handler is passed to the child component as a props. If there is any update to the state that occurs in the parent, it will re-render the parent component though the child component is not dependent on the state which is updated. So this makes an extra re-rendering falls to performance issue.

To solve these issues, we can useCallback hooks that cache the function itself because each render of component creates new instance of the function.

In the below example, onclick of the toggle button the counter component was re-rendering each time so we used callback hooks to stop re-rendering

    import React, {useState, useEffect, useCallback} from 'react';
    
    const IncrementCounter = (props) => {
        useEffect(() => {
            console.log('Counter Function Called');
        }, [props.incrementVal]);
        return (
            <div>
                <button onClick={() => props.incrementVal(1)}>Increment Counter</button>
                <p>Counter Val: {props.counter}</p>
            </div>
        )
    }
    
    function Question2CallBack(props) {
        const [toggle, setToggle] = useState(false);
        const [counter, setCounter] = useState(0);
    
        // USING USECALLBACK FOR CATCHING THE SAME REFRENCE OF FUNCTION WHILE EACH RENDER DURING STATE CHANGE
        const incrementVal = useCallback((val) => {
            setCounter(()=> counter + val);
        }, [counter]);
    
        return (
            <div>
                <button onClick={() => setToggle(!toggle)}>Toggle</button>
                <p>Toggle Value: {toggle ? 'true': 'false'}</p>
                <IncrementCounter incrementVal={incrementVal} counter={counter}/>
            </div>
        );
    }
    
    export default Question2CallBack;
Yun answered 6/8, 2023 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.