DEV Community

loading...

How to useCallback()

spukas profile image Linas Spukas ・2 min read

React.useCallback() is a hook that takes a function and a list of dependencies as arguments. If none of the passed dependencies changes, the hook returns memoized, or a cached version of the callback. If at least one of the dependencies changes, it returns a newly created callback.

React.useCallback(fn, deps)

Problem

When you are passing a callback to child components, every time the parent component re-renders, it creates a new declared function. And when a child component receives new props, even it has an equality check, it re-renders as well.
Just to remind you, in JavaScript () => {} === () => {} or {} === {} returns false.

A quick tip: to check if a component creates new callbacks or other values, you can create a new Set() and add values on every render. A Set() will add only the unique values:

const store = new Set();

function Parent() {
  const someFn = () => {};

  store.add(someFn);
  console.log(store);

  return(
    <Child callback={someFn} />
  );
}

function Child({ callback }) { ... };

In the example above, on every Parent render, you should get a console log of the Set() with added functions.
And for each newly created function, JavaScript must allocate some memory. Which is not an issue in the small project, but if you have a massive list to loop and pass the callbacks, this would be the case where you should think about using a React.useCallback().

Solution

To escape creating new functions every time, we can wrap a function inside useCallback() hook and add a list of dependencies. If one of the dependencies in the list changes, the callback will be recreated.

function Parent() {
  const memoizedCallback = React.useCallback(() => {}, []);

  return(
    <MemoizedChild callback={memoizedCallback} />
  );
}

const MemoizedChild = React.memo(({ callback }) => { ... });

In this example, we have a memoized child component. That means, that component checks current and new props, and if they differ, the component re-renders.
But that is not enough if we do not memoize a callback, because a new callback will be created after the parent component re-renders.
By wrapping a function with React.useCallback(() => {}, []) and specifying empty dependency list [], component caches function and makes sure it will not be created on the following renders. Which means we will always pass the same function to the child component, and it never re-renders unnecessarily again.
As a rule of thumb you should always specify dependencies, that are used in the callback, for example:

React.useCallback(() => {
  setPrice(amount, quantity)
}, [amount, quantity]);

Conclusion

To sum up, useCallback() should be used when passing callbacks from the parent component to the child, especially when the child components are optimized for performance. Are memoized or dependant on the equality check, like shouldComponentUpdate(). And make sure always to add a list of dependencies, when the function should be recreated.

Discussion (3)

pic
Editor guide
Collapse
squigglybob profile image
squigglybob

I'm struggling with this issue right now, and my Lint errors are telling me to put the function as a dependency (which continues the infinite loop saga). How come you didn't need to add setPrice to your dependency array above?

Collapse
typekev profile image
Kevin Gonzalez

The linter will complain in this case too.

You can see this GitHub issue for a whole book on the topic: github.com/facebook/react/issues/1...

But you definitely can include it in the deps array. Just make sure that setPrice is memo'd.

Collapse
wsirota profile image
wsirota

Thanking you for the brilliantly concise explanation of meaning, usage, and use case.