React hooks: is `useCallback` not so needed usually?
Asked Answered
C

2

10

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?

Ciborium answered 30/10, 2019 at 6:48 Comment(7)
Kent's article pretty much sums up with the same conclusionBaliol
The only situation where I found it was needed, is if the callback eventually ended up into a use memo or use effectHorus
Passing a callback from a list component to it's items would DOM re render all items if the callback would cause an item to change. This because the callback would have a new reference every time and virtual DOM compare would fail. So if you have 100 items and change one then 99 unchanged items will DOM render for no reason here is an example to prevent that with stateful components and here with react-redux container.Shaft
Since this is Kent's opinion I would give my opinion to the contrary. When using reselect and pure components it would not make sense to not use useCallback because not using it would making the pure component and memoizing calculated props from state useless. Re rendering components when nothing has changed for that component can make debugging difficult as well.Shaft
I think the main reason he gives for not using it is: it can be tricky to get right all the time so you may not be reaping any benefits at all anyway. So in other words; "it's difficult so don't bother". I do agree that it is difficult but gets a lot easier when you define containers / presentational components and use selectors in your containers (as you should already anyway).Shaft
Looks like the answer is yes unless you have a child component that is expensive to render. But with react class components we never had to worry about this aspect and now with react hooks we need to watch-out for expensive re-renders. In my case, I have a child component that renders a chart and is being updated for every cycle.Starknaked
Next time when the documentation tells me "MOST OF THE TIME YOU SHOULD NOT", i'll take it as a "Don't do this".Smokedry
H
10

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.

Hern answered 30/10, 2019 at 9:1 Comment(0)
S
1

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.

Smokedry answered 28/8, 2021 at 16:34 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.