Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the difference between `useCallback` with an empty array as inputs and `useCallback` without a second parameter?

On my journey to try and understand React Hooks better I came across some behaviour I did not expect. I was attempting to create an array of refs and pushing to said array via an onRef function I would pass to my <div>'s. The array kept getting bigger everytime the component re-rendered presumably just because it was a simple arrow function and not memoized.

So then I added the useCallback hook to make sure that I wouldn't get the same ref multiple times, but to my surprise it still called the function every re-render. After adding an empty array as second parameter the refs only fired once per component as expected.

This behaviour is demonstrated in the snippet below.

const Example = () => {
  const _refs = React.useRef([]);
  
  // Var to force a re-render.
  const [ forceCount, forceUpdate ] = React.useState(0);
  
  const onRef = (ref) => {
    if (ref && ref !== null) {
      console.log("Adding Ref -> Just an arrow function");
      _refs.current.push(ref);
    }
  }
  
  const onRefCallbackWithoutInputs = React.useCallback((ref) => {
    if (ref && ref !== null) {
      console.log("Adding Ref -> Callback without inputs.");
      _refs.current.push(ref);
    }
  });
  
  const onRefCallbackEmptyArray = React.useCallback((ref) => {
    if (ref && ref !== null) {
      console.log("Adding Ref -> Callback with empty array");
      _refs.current.push(ref);
    }
  }, []);
  
  React.useEffect(() => {
    console.log("Refs size: ", _refs.current.length);
  });
  
  return (
    <div>
      <div ref={onRef}/>
      <div ref={onRefCallbackWithoutInputs}/>
      <div ref={onRefCallbackEmptyArray}/>
      <div onClick={() => forceUpdate(forceCount + 1)} 
        style = {
          {
            width: '100px',
            height: '100px',
            marginTop: '12px',
            backgroundColor: 'orange'
          }
        }>
        {'Click me to update'}
       </div>
    </div>
  );
};

ReactDOM.render(<Example/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root' style='width: 100%; height: 100%'>
</div>

I assumed useCallback would have an empty array as a default for the second parameter. So what exactly does not giving a second parameter do? Why does it behave differently?

like image 898
ApplePearPerson Avatar asked Mar 06 '19 15:03

ApplePearPerson


People also ask

What is the second parameter of useCallback?

The syntax of useCallback hook Similarly to useEffect hook, useCallback also accepts two parameters. The first parameter is the function you want to memoize. The second parameter is an array of dependencies. This array of dependencies specifies values React should watch.

Can useCallback take parameters?

The syntax As we can see, the useCallback React hook takes in an inline function and its dependencies as parameters and returns a memoized version of the function.

What is the difference between useCallback and useMemo?

useMemo is very similar to useCallback. It accepts a function and a list of dependencies, but the difference between useMemo and useCallback is that useMemo returns the memo-ized value returned by the passed function. It only recalculates the value when one of the dependencies changes.

For what purpose is the useCallback () hook used?

The useCallback hook is used when you have a component in which the child is rerendering again and again without need. Pass an inline callback and an array of dependencies. useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed.


3 Answers

For both useMemo and useCallback (which is essentially just a special case of useMemo), if the second argument is an empty array, the value will be memoized once and always returned.

If the second argument is omitted, the value will never be memoized, and the useCallback and the useMemo doesn't do anything.

Perhaps there's some edge case where you might conditionally memoize:

useMemo(someValue, shouldMemoize ? [] : null) 

But in the vast majority of cases, the second argument to both useMemo and useCallback should be considered mandatory. And in fact, the Typescript definitions treat them this way.

// Require a second argument, and it must be an array function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;  // Second argument can be undefined, but must be explicitly passed as undefined, not omitted. function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T; 

There's an open pull request that's enhancing the exhaustive-deps hooks eslint rule so that it will raise a lint error if the second argument is omitted, so pretty soon this will likely be a linter error.

like image 198
Retsam Avatar answered Sep 20 '22 20:09

Retsam


I think it's the same logic behind all the hooks, useEffect, useLayoutEffect, useCallback, useMemo, for dependency array, if no dependencies passed means we passed the null value for dependencies, hence comparison would always result false and inline function will execute every time.

If empty dependencies are passed means there is nothing to compare further hence inline function will only execute once. (it is just like we are instructing React for no further comparison).

If the array are passed with some variable then it will compute the inline function based on the changes in the variable.

Though instance of the inline function will always created.

like image 39
Krishna Agarwal Avatar answered Sep 20 '22 20:09

Krishna Agarwal


useCallback with an empty dependency array is a memoized function, which does not compute updated states (if you have states inside it, it'll use the initial value passed in useState.

When you want your useCallback function to consume a state, for example, you should pass it inside its dependency array, just like useEffect works! Doing this, your whole function will be remounted everytime that state changes.

like image 34
antoniopataro Avatar answered Sep 17 '22 20:09

antoniopataro