In React useCallback
hook is a built-in hook that optimizes the performance of functional components by memoizing callback functions.
It allows us to prevent unnecessary re-rendering of child components when the parent component re-renders.
In this blog post, we will dig deeper into useCallback
hook. You will 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 useCallback Hook?
The main purpose of React useCallback
hook is to memoize functions. The main reason for this is increasing performance of your React applications.
How is this related? Every time your component re-renders it also re-creates functions that are defined inside it. Memoizing functions helps you prevent this.
🤔 What is memoization?
It does this by storing computation results in cache, and retrieving that same information from the cache the next time it’s needed instead of computing it again.
Memoization is a simple but powerful trick that can help speed up our code, especially when dealing with repetitive and heavy computing functions.
For example, if there is a function to add two numbers, and we give the parameter as 1 and 2 for the first time the function will add these two numbers and return 3.
But if the same inputs come again then we will return the cached value i.e 3 and not compute with the add function again.
When you memoize a function with useCallback
hook that function is basically stored in cache.
Quick example. Imagine that something causes your component to re-render. Let’s say there is a change of state. Usually, this re-render would, by default, also cause React to re-create all functions defined in your component.
This may not happen with useCallback
hook and memoization. When you memoize a function React may not re-create that function just because the component re-rendered.
Instead, React can skip the re-creation and return the memoized function. This can help you save resources and time and improve performance of your application.
Syntax of useCallback Hook
The syntax of useCallback
hook is as follows:
const memoizedCallback = useCallback(callback, dependencies);
- callback: The function that we want to memorize.
- dependencies (optional): An array of dependencies that, when changed, will trigger the recreation of the memoized function. If no dependencies are provided, the memoized function will be recreated on every render.
More simplified version of useCallback
hook:
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
It looks like the useEffect hook, the first argument of useCallback
is the function that you want to memoize.
The second argument is an optional array of dependencies that are used to determine whether the memoized function should be recomputed.
If any of the dependencies change, the memoized function will be re-computed; otherwise, the cached function will be returned.
Always remember the primary benefit of useCallback
is that it helps to prevent unnecessary re-renders of child components.
Understanding Functions Equality Check
Before diving into useCallback
hook use, let’s distinguish the problem useCallback
hook solves — referential equality and function equality.
Functions in JavaScript are first-class citizens, meaning that functions here are treated like any other variable. They can be passed as arguments, returned by another function, or assigned as a value to a variable.
Also Read: Higher Order Functions in JavaScript
In short, it can do anything that an object can do in JavaScript.
Let’s write a function factory()
that returns functions that sum numbers:
function factory() { return (a, b) => a + b; } const sumFunc1 = factory(); const sumFunc2 = factory(); console.log(sumFunc1(1, 2)); // => 3 console.log(sumFunc2(1, 2)); // => 3 console.log(sumFunc1 === sumFunc2); // => false console.log(sumFunc1 === sumFunc1); // => true
sumFunc1
and sumFunc2
are functions that sum two numbers. They have been created by the factory()
function.
The functions sumFunc1
and sumFunc2
share the same code source, but they are different function objects. Comparing them sumFunc1 === sumFunc2
evaluates to false.
That’s just how JavaScript objects work. An object (including a function object) equals only to itself.
But how it related to React?
When React re-renders your component, it also recreates the functions you have declared inside your component.
As we have seen already 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.
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
hook to help you out.
The Purpose of useCallback Hook
Going back to React, when a component re-renders, every function inside of the component is recreated and therefore these functions’ references change between renders.
That’s when useCallback(callback, dependencies) is helpful: given the same dependency values, the hook returns the same function instance between renderings (aka memoization).
const memoized = useCallback(() => {
// the callback function to be memoized
},
// dependencies array
[]);
For Example:
import { useCallback } from 'react'; function MyComponent() { // handleClick is the same function object const handleClick = useCallback(() => { console.log('Clicked!'); }, []); // ... }
handleClick
variable has always the same callback function object between renderings of MyComponent
.
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
hook makes more sense:
- You are 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
Use Case Scenario : useCallback Hook
Let’s take a simple application as case study. This simple app consists of 3 components, Todo
, Number
and Counter
.

The Todo
component displays items that were passed from props
.
The Number
component displays the current value of number while the Counter
component is responsible for the 2 buttons that can change the value of number.
These 3 components are wrapped inside App.jsx as shown below.
App.jsx
import { useState } from 'react'; import './App.css'; import Counter from './Counter'; import Number from './Number'; import Todo from './Todo'; function App() { console.log('Render App'); //init items for Todo const [items, setItems] = useState([ '1. Some todo', '2. Some todo', '3. Some todo', ]); //add items for Todo const add = () => { setItems(() => [...items, 'New todo']); }; //init number for Number and Counter const [number, setNumber] = useState(0); //handler function for Counter const increase = () => { setNumber(number + 1); }; //handler function for Counter const decrease = () => { setNumber(number - 1); }; return ( <div> <Todo items={items} add={add} /> <Number number={number} /> <Counter incr={increase} decr={decrease} /> </div> ); } export default App;
As seen in the code, all the states and functions are within App.jsx while the 3 child components are merely acting as separate containers for this example.
In each component, we have a console.log statement to track when they are rendered.
In Todo.jsx
import React from 'react'; const Todo = (props) => { console.log('Render Todo'); return ( <div> <h2>My Todo</h2> {props.items.map((item, index) => { return <p key={index}>{item}</p>; })} <button onClick={props.add}>Add Todo</button> </div> ); }; export default Todo;
In Number.jsx
import React from 'react'; const Number = (props) => { console.log('Render Number'); return ( <div> <h2>Counter</h2> <p>The number is: {props.number}</p> </div> ); }; export default Number;
In Counter.jsx
import React from 'react'; const Counter = (props) => { console.log('Render Counter'); return ( <div> <button onClick={props.incr}>Increase</button> <button onClick={props.decr}>Decrease</button> </div> ); }; export default Counter;
If we run our app, notice in the clip below that the console logs when a component is re-rendered.
If we only click the Counter
buttons and update the number value, the Todo
component also re-renders along with Number
and Counter
. It is unnecessary for Todo
to re-render if its values stay the same.

It goes for the same when an item is added to the Todo
component and the items array gets updated. Both Counter
and Number
gets unnecessarily re-rendered even though they have nothing to do with items.
This can be an issue if a component is very huge and needs to load over a hundred items on its list.
If it keeps being re-rendered even when its items do not change, it can cause performance issues in the app. There is no need to re-render the components that unrelated to a particular state update.
Solution
React memo is a built-in React feature that renders a memoized component and skip unnecessary re-rendering. So each component will only re-render if it detects a change in their props.
So, we can wrap the component with memo()
in its export line:
export default memo(Component);
Wrap memo around the Todo
, Number
and Counter
components.
Todo.jsx
import { memo } from 'react'; const Todo = (props) => { console.log('Render Todo'); return ( <div> <h2>My Todo</h2> {props.items.map((item, index) => { return <p key={index}>{item}</p>; })} <button onClick={props.add}>Add Todo</button> </div> ); }; export default memo(Todo);
Number.jsx
import { memo } from 'react'; const Number = (props) => { console.log('Render Number'); return ( <div> <h2>Counter</h2> <p>The number is: {props.number}</p> </div> ); }; export default memo(Number);
Counter.jsx
import { memo } from 'react'; const Counter = (props) => { console.log('Render Counter'); return ( <div> <button onClick={props.incr}>Increase</button> <button onClick={props.decr}>Decrease</button> </div> ); }; export default memo(Counter);
At this point, you might think: Great! That should be all. But let’s see the app in action:
Uh-oh! Why is it still rendering unnecessary components? When the Add Todo button is clicked, we expect only App
and Todo
to re-render.
Instead, we get App
, Todo
and Counter
, but Number
is behaving correctly.

Why is this so?
Let’s go back and see how App returns the 3 components.
return ( <div> <Todo items={items} add={add} /> <Number number={number} /> <Counter incr={increase} decr={decrease} /> </div> );
As you can see, the 3 functions written in App: add, increase and decrease are passed as props in Todo
and Counter
.
Notice that only the value number is passed into Number
as props.
In React, whenever a component re-renders, a new instance of the function in it gets generated.
Therefore, every time App renders, add, increase and decrease are re-created.
So their references now points to different functions in memory. Hence, in terms of referential equality, the functions before re-render are not the same as the functions after the re-render.
See diagram below to visualize.

As a result, when Add Todo button is clicked:
- App gets re-rendered.
- The items array and the references for add, increase and decrease gets updated.
- React memo accounts for these changes and re-renders the components with items, add, increase and decrease as their props.
On the other hand, Number
does not get re-rendered when Add Todo button is clicked because there is no change to the number prop.
So how do we prevent the reference of the functions the same?
useCallback to The Rescue
As previously mentioned, the Hook takes a callback function as its argument and a dependency array as its second.
To solve the issue in our example, we simply need to wrap our handler functions in App.jsx: add, increase and decrease inside the Hook.
This prevents the unnecessary re-rendering behaviour because it ensures the same callback function reference is returned when there is no change in their dependency.
For example, let’s edit the add function first.
//add items for Todo - before const add = () => { setItems(() => [...items, "New todo"]); }; //add items for Todo - after const add = useCallback(() => { setItems(() => [...items, "New todo"]); }, [items])
Now, the function add will only be updated if items change. Do the same for increase and decrease functions.
These functions should only be updated when number changes. They should look like:
const increase = useCallback(() => { setNumber(number + 1); }, [number]); const decrease = useCallback(() => { setNumber(number - 1); }, [number]);
App.jsx
import { useState, useCallback } from 'react'; import './App.css'; import Counter from './Counter'; import Number from './Number'; import Todo from './Todo'; function App() { console.log('Render App'); //init items for Todo const [items, setItems] = useState([ '1. Some todo', '2. Some todo', '3. Some todo', ]); //add items for Todo const add = useCallback(() => { setItems(() => [...items, 'New todo']); }, [items]); //init number for Number and Counter const [number, setNumber] = useState(0); //handler function for Counter const increase = useCallback(() => { setNumber(number + 1); }, [number]); //handler function for Counter const decrease = useCallback(() => { setNumber(number - 1); }, [number]); return ( <div> <Todo items={items} add={add} /> <Number number={number} /> <Counter incr={increase} decr={decrease} /> </div> ); } export default App;
Result:

Yay! Now only the relevant components will re-render. add only result in Todo
updating and re-rendering. And increase or decrease will re-render Number
and Counter
components.

Conclusion
The React useCallback
hook can be useful for improving performance of your apps, by storing your functions for later use, instead of re-creating them on every re-render.
This can improve re-rendering behavior and performance of heavy components.
And that’s the gist of this Hook! Thanks for reading this article.
We hope it was helpful for React beginners. Please feel free to ask questions in the comments below. Ultimately, practicing and building projects with useCallback
hook will help anyone to pick it up faster.
Add comment