How to Use useCallback to Write Better React Code
React hooks, a new method to manage state in functional components, was introduced in React v16.8. With hooks like useState
, useEffect
, and others, developers could finally work with side effects in functional components.
In this article, we’ll be taking a close look at useCallback
, one of the introduced React hooks, and how we can use it to write better React code.
Introduction to useCallback
useCallback
is used to optimize the rendering behavior of React functional components. It’s useful when a component is being constantly re-rendered and there’s complex function behavior inside of it. We’ll be taking a look at a simple example of how to implement this hook to see how it can help us gain efficiency in re-rendering components.
Keep in mind that React is very fast already, performance optimizations should be used only if a React component begins to feel slow, we’ll take a look at when to use useCallback
at the end of this article
Lets take a look at a very simple Counter
React component:
This is pretty simple, we have two state variables that are holding numbers and three functions to change our state. However, the problem here is that every time the Counter
component is re-rendered, all three functions, increment
, decrement
, and incrementOtherCounter
are all recreated!
We can see that by using a Set
and adding the function to the Set each time the function runs. Why Set? It only stores unique elements, or in our case, uniquely instantiated functions.
Now, every time we click a button we should see the functionsSet
being logged, and increasing by three each time! This shows us that every time the component is re-rendered, entirely new instances of functions are created, an necessary operation.
At its core, this problem is due to the how Javascript determines function equality.
Function Equality in Javascript
Lets look at some example code where we have a factory
function that’s returning a function:
Here, we create function1
and function2
using the factory
. Functions in Javascript are first-class citizens, meaning that they’re represented as regular objects. The function object can be returned by other functions (like factory
), compared, and or anything a normal object can do.
You can see that although function1
and function2
both come from the same place and do the same thing, they’re inherently different objects, so they aren’t equal. Any Javascript function is only equal to itself.
Going back to React, when a component re-renders, every function inside of the component is recreated. useCallback
makes it so we can memoize (or cache) the function instance between renders. This means that instead of recreating the function object, we can use the same function object between renders.
Lets update our counter increment/decrement functions to use useCallback
Notice how we use a dependency array as one of the function parameters of useCallback
. As long as the values in the dependency array are the same between renders, React will continue using the memoized (or cached) version of the function. If the values in the dependency array change between renders, React will recreate the function.
In this case, our logs will now reflect this. When we click the button to run incrementOtherCounter
, our functionsSet
will only increase by one, since only count2
is being updated, so only one function is being recreated (incrementOtherCounter
). We know this because count2
is only in the dependency array of incrementOtherCounter
and not either of the other functions.
When we click either the +
or -
buttons, functionsSet
will increase by two, since we’re recreating both the increment
and decrement
functions.
Although this is a very simple example, we can see how we can use useCallback
to optimize components that have complex or resource intensive functions.
When not to use useCallback
However, let’s ensure we don’t go too overboard. useCallback
has its own downsides, primarily code complexity. There are a lot of cases in which it doesn’t make sense to add useCallback
and just accept function recreation. useCallback
has its own performance drawbacks, as it still has to run on every component re-render.
In this example, useCallback
is actually not helping optimization, since we’re creating handleClick
on every render anyways:
In this case, the optimization costs more than not having the optimization.
When thinking about performance upgrades like useCallback
, always profile (or measure) your component speed before beginning the optimization process. Optimization adds complexity, and as a developer you always want to make sure that this tradeoff is worth it.
Conclusion
useCallback
is a very powerful React hook to optimize complex React components by memoizing functions to prevent recreation upon every render.
Before working with useCallback
, make sure you analyze the following:
- Does the speed increase actually exist with
useCallback
? - Does the speed increase warrant increased component complexity?
For more information on useCallback
, I recommend looking at the React documentation. How are you using useCallback
? I’d love to hear about it in the comments!
Keep in Touch
There’s a lot of content out there and I appreciate you reading mine. I’m a undergraduate student at UC Berkeley in the MET program and a young entrepreneur. I write about software development, startups, and failure (something I’m quite adept at). You can signup for my newsletter here or check out what I’m working on at my website.
Feel free to reach out and connect with me on Linkedin or Twitter, I love hearing from people who read my articles :)