Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React hooks: is `useCallback` not so needed usually?

I am recently refactoring a web app using React Hooks. I encounter a problem regarding useCallback. Based on description of Kent: https://kentcdodds.com/blog/usememo-and-usecallback, useCallback is to pass in identical function reference to sub-components, to avoid re-render of sub-components, so that the performance is better. However, it's used together with React.memo. And as Kent said:

MOST OF THE TIME YOU SHOULD NOT BOTHER OPTIMIZING UNNECESSARY RERENDERS. React is VERY fast and there are so many things I can think of for you to do with your time that would be better than optimizing things like this. In fact, the need to optimize stuff with what I'm about to show you is so rare that I've literally never needed to do it ...

So, my question is: am I right to claim that we do not need to use useCallback usually? except when the callback is expensive to create, using useCallback avoids re-creating the callback for every render.

Say, for a onClick or onChange event handler, 2 lines or less, shall we just not use useCallback to wrap it?

like image 388
Joy Avatar asked Oct 30 '19 06:10

Joy


People also ask

When should you not use useCallback?

Feel free to remove all useMemo and useCallbacks from the code if: they passed as attributes, directly or through a chain of dependencies, to DOM elements. they passed as props, directly or through a chain of dependencies, to a component that is not memoized.

Why we use useCallback hook in React?

One reason to use useCallback is to prevent a component from re-rendering unless its props have changed. In this example, you might think that the Todos component will not re-render unless the todos change: This is a similar example to the one in the React.memo section.

Should I use useCallback or useMemo?

With useCallback you can define a function that has referential equality between renders. You can use useMemo to calculate a value that has referential equality between renders.

What can I use instead of useCallback?

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.


2 Answers

I find the useCallback() is necessary when I don't want the function reference to change. For example, when I'm using React.memo on some child component that should not be re-rendered as a result of a reference change in one of its methods that comes through props.

Example:

In the example below Child1 will always re-render if Parent re-renders, because parentMethod1 will get a new reference on every render. And Child2 will not re-render, because the parentMethod2 will preserve its reference across renders (you can pass a dependency array to make it change and be re-created when new input values come).

Note: Assuming the Child components are being memoized with React.memo()

function Parent() {
  const parentMethod1 = () => DO SOMETHING;
  const parentMethod2 = useCallback(() => DO SOMETHING,[]);
  return(
    <React.Fragment>
    <Child1
      propA=parentMethod1
    />
    <Child2
      propA=parentMethod2
    />
    </React.Fragment>
  );
}

On the other hand, if the function is expensive to run, you can memoize its results using the useMemo hook. Then you will only run it when new values come, otherwise it will give you a memoized result from previous calculations using those same values.

https://reactjs.org/docs/hooks-reference.html#usecallback

useCallback

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. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders (e.g. shouldComponentUpdate).

useMemo

Pass a “create” function and an array of dependencies. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

like image 156
cbdeveloper Avatar answered Sep 23 '22 04:09

cbdeveloper


I think you are right. From how it's designed, useCallback should be almost useless in React. It can not be used directly to prevent a child render.

What can save a child render is to wrap entire render using a useMemo.

const Title = () => {
  ...
  const child = useMemo(() => {
    return <Child a={"Hello World"} />
  }, [])
  return (
    <>
      {child}
      <div onClick={onClick}>{count}</div>
    </>
  )
}

The above approach is a bit different than React.memo because it acts directly on the parent Title, instead of Child. But it's more or less answer your question why it's useless, except when you use it as the shortcut for useMemo.

Article explaining this, https://javascript.plainenglish.io/can-usememo-skip-a-child-render-94e61f5ad981

back to useCallback

Now let's go back to see if a callback wrapped with or without useCallback is useful.

<div onClick={onClick}>kk</div>

The only thing it might save is that when it's under reconciliation, onClick (with useCallback) points to the same function instance.

However I don't know if React actually does any optimization at that step. Because assigning a different callback to the attribute might take additional memory and time. But adding a new variable in general takes additional memory as well.

So this type of optimization is more like a coding optimization, more or less subjective. Not objective enough to be applied in a solid case.

Of course, if you want to fix a function instance for any third party function, ex. debounce. That might be a good use, but still smell fishy, because useMemo seems much more versatile to cover this case as well.

All in all, I'm only pointing out, useCallback isn't doing what's the public believe it can do, such as to bailout child component.

like image 26
windmaomao Avatar answered Sep 24 '22 04:09

windmaomao