1. Understand the useCallback Hook: The useCallback hook is a React hook that allows you to memoize functions so that they are only created once and then reused on subsequent renders. This can be useful for optimizing performance when you have expensive functions that are called frequently.
2. Know When to Use the useCallback Hook: The useCallback hook should be used when you have a function that is called frequently and is expensive to create. This could be a function that is used in a render prop, a custom hook, or a component.
3. Use the useCallback Hook: To use the useCallback hook, you need to pass in a function and an array of dependencies. The dependencies are variables that the function depends on. If any of the dependencies change, the function will be recreated.
4. Understand the Benefits of the useCallback Hook: The useCallback hook can help improve performance by avoiding unnecessary re-renders and by avoiding the creation of expensive functions. It can also help make code more readable by avoiding the need to create multiple functions with the same logic.
It’s no secret that React.js has become widely popular in recent years. It’s now the JavaScript library of choice for many of the internet’s most prominent players, including Facebook and WhatsApp.
One of the main reasons for its rise was the introduction of hooks in version 16.8. React hooks allow you to tap into React functionality without writing class components. Now functional components with hooks have become developers’ go-to structure for working with React.
In this blog post, we’ll dig deeper into one specific hook — useCallback
— because it touches on a fundamental part of functional programming known as memoization. You’ll know exactly how and when to utilize the useCallback
hook and make the best of its performance-enhancing capabilities.
Ready? Let’s dive in!
What Is Memoization?
Memoization is when a complex function stores its output so the next time it is called with the same input. It’s similar to caching, but on a more local level. It can skip any complex computations and return the output faster as it’s already calculated.
This can have a significant effect on memory allocation and performance, and that strain is what the useCallback
hook is meant to alleviate.
React’s useCallback vs useMemo
At this point, it’s worth mentioning that useCallback
pairs nicely with another hook called useMemo
. We’ll discuss them both, but in this piece, we’re going to focus on useCallback
as the main topic.
The key difference is that useMemo
returns a memoized value, whereas useCallback
returns a memoized function. That means that useMemo
is used for storing a computed value, while useCallback
returns a function that you can call later on.
These hooks will give you back a cached version unless one of their dependencies (e.g. state or props) changes.
Let’s take a look at the two functions in action:
import { useMemo, useCallback } from 'react'
const values = [3, 9, 6, 4, 2, 1]
// This will always return the same value, a sorted array. Once the values array changes then this will recompute.
const memoizedValue = useMemo(() => values.sort(), [values])
// This will give me back a function that can be called later on. It will always return the same result unless the values array is modified.
const memoizedFunction = useCallback(() => values.sort(), [values])
The code snippet above is a contrived example but shows the difference between the two callbacks:
memoizedValue
will become the array[1, 2, 3, 4, 6, 9]
. As long as the values variable stays, so willmemoizedValue
, and it’ll never recompute.memoizedFunction
will be a function that will return the array[1, 2, 3, 4, 6, 9]
.
What’s great about these two callbacks is they become cached and hang around until the dependency array changes. This means that on a render, they won’t get garbage collected.
Rendering and React
Why is memoization important when it comes to React?
It has to do with how React renders your components. React uses a Virtual DOM stored in memory to compare data and decide what to update.
The virtual DOM helps React with performance and keeps your application fast. By default, if any value in your component changes, the entire component will re-render. This makes React “reactive” to user input and allows the screen to update without reloading the page.
You don’t want to render your component because changes won’t affect that component. This is where memoization through useCallback
and useMemo
comes in handy.
When React re-renders your component, it also recreates the functions you’ve declared inside your component.
Note that when comparing the equality of a function to another function, they will always be false. Because a function is also an object, it will only equal itself:
// these variables contain the exact same function but they are not equal
const hello = () => console.log('Hello Matt')
const hello2 = () => console.log('Hello Matt')
hello === hello2 // false
hello === hello // true
In other words, when React re-renders your component, it will see any functions that are declared in your component as being new functions.
This is fine most of the time, and simple functions are easy to compute and will not impact performance. But the other times when you don’t want the function to be seen as a new function, you can rely on useCallback
to help you out.
You might be thinking, “When would I not want a function to be seen as a new function?” Well, there are certain cases when useCallback
makes more sense:
- You’re passing the function to another component that is also memoized (
useMemo
) - Your function has an internal state it needs to remember
- Your function is a dependency of another hook, like
useEffect
for example
Performance Benefits of React useCallback
When useCallback
is appropriately used, it can help speed up your application and prevent components from re-rendering if they don’t need to.
Let’s say, for example, you have a component that fetches a large amount of data and is responsible for displaying that data in the form of a chart or a graph, like this:
Suppose the parent component for your data visualization’s component re-renders, but the changed props or state do not affect that component. In that case, you probably don’t want or need to re-render it and refetch all the data. Avoiding this re-render and refetch can save your user’s bandwidth and provide a smoother user experience.
Drawbacks of React useCallback
Although this hook can help you improve performance, it also comes with its pitfalls. Some things to consider before using useCallback
(and useMemo
) are:
- Garbage collection: The other functions that are not already memoized will get thrown away by React to free up memory.
- Memory allocation: Similar to garbage collection, the more memoized functions you have, the more memory that’ll be required. Plus, each time you use these callbacks, there’s a bunch of code inside React that needs to use even more memory to provide you with the cached output.
- Code complexity: When you start wrapping functions in these hooks, you immediately increase the complexity of your code. It now requires more understanding of why these hooks are being used and confirmation that they’re used correctly.
Being aware of the above pitfalls can save you the headache of stumbling across them yourself. When considering employing useCallback
, be sure the performance benefits will outweigh the drawbacks.
React useCallback Example
Below is a simple setup with a Button component and a Counter component. The Counter has two pieces of state and renders out two Button components, each that will update a separate part of the Counter components state.
The Button component takes in two props: handleClick
and name. Each time the Button is rendered, it will log to the console.
import { useCallback, useState } from 'react'
const Button = ({handleClick, name}) => {
console.log(`${name} rendered`)
return <button onClick={handleClick}>{name}</button>
}
const Counter = () => {
console.log('counter rendered')
const [countOne, setCountOne] = useState(0)
const [countTwo, setCountTwo] = useState(0)
return (
<>
{countOne} {countTwo}
<Button handleClick={() => setCountOne(countOne + 1)} name="button1" />
<Button handleClick={() => setCountTwo(countTwo + 1)} name="button1" />
</>
)
}
In this example, whenever you click on either button, you’ll see this in the console:
// counter rendered
// button1 rendered
// button2 rendered
Now, if we apply useCallback
to our handleClick
functions and wrap our Button in React.memo
, we can see what useCallback
provides us. React.memo
is similar to useMemo
and allows us to memoize a component.
import { useCallback, useState } from 'react'
const Button = React.memo(({handleClick, name}) => {
console.log(`${name} rendered`)
return <button onClick={handleClick}>{name}</button>
})
const Counter = () => {
console.log('counter rendered')
const [countOne, setCountOne] = useState(0)
const [countTwo, setCountTwo] = useState(0)
const memoizedSetCountOne = useCallback(() => setCountOne(countOne + 1), [countOne)
const memoizedSetCountTwo = useCallback(() => setCountTwo(countTwo + 1), [countTwo])
return (
<>
{countOne} {countTwo}
<Button handleClick={memoizedSetCountOne} name="button1" />
<Button handleClick={memoizedSetCountTwo} name="button1" />
</>
)
}
Now when we click either of the buttons, we’ll only see the button we clicked to log into the console:
// counter rendered
// button1 rendered
// counter rendered
// button2 rendered
We’ve applied memoization to our button component, and the prop values that are passed to it are seen as equal. The two handleClick
functions are cached and will be seen as the same function by React until the value of an item in the dependency array changes (e.g. countOne
, countTwo
).
Summary
As cool as useCallback
and useMemo
are, remember that they have specific use cases — you should not be wrapping every function with these hooks. If the function is computationally complex, a dependency of another hook or a prop passed to a memoized component are good indicators that you might want to reach for useCallback
.
We hope this article helped you understand this advanced React functionality and helped you gain more confidence with functional programming along the way!
Get all your applications, databases and WordPress sites online and under one roof. Our feature-packed, high-performance cloud platform includes:
- Easy setup and management in the MyKinsta dashboard
- 24/7 expert support
- The best Google Cloud Platform hardware and network, powered by Kubernetes for maximum scalability
- An enterprise-level Cloudflare integration for speed and security
- Global audience reach with up to 35 data centers and 275 PoPs worldwide
Get started with a free trial of our Application Hosting or Database Hosting. Explore our plans or talk to sales to find your best fit.