Skip to main content

useCallback

The React useCallback hook returns a memoized callback function.

:::info Memoization Think of memoization as caching a value so that it does not need to be recalculated. :::

This allows us to isolate resource intensive functions so that they will not automatically run on every render. The useCallback Hook only runs when one of its dependencies update. This can improve performance.

We can best illustrate this with an example.

File src/Components/Customers.tsx

import { memo } from 'react'

const Customers = ({ customers, addCustomer }) => {

console.log("Re-render of customers??")

return(
<div>
<h2>Customers</h2>
<ul>
{
customers.map(( customer, index) => {
return(
<li key={`cust-${index}`}>{ customer }</li>
)
})
}
</ul>
<button onClick={ addCustomer }>Add</button>
</div>
)
}

export default memo(Customers)

Breakdown

Very basic component, except for two things:

  • line 1: it imports the memo Higher Order component to wrap things up.
  • line 24: it wraps the Customers in this memo function so React knows how to handle it.
  • line 19: the addCustomer function is passed from App

1. Without useCallback

File src/App.tsx


import {useState, useCallback} from "react";
import Customers from "./components/Customers";

const App = () => {

const [count, setCount] = useState(2)
const [customers, setCustomers] = useState(["Klant #1"]);
const [unrelatedCount, setUnrelatedCount] = useState(0)

const addCustomer = () => {
setCustomers((c) => [...c, `Customer #${count}`])
setCount( c => c + 1)
}

const unrelated = () => {
setUnrelatedCount( (c)=> c+1)
}

return (
<div className="App">
<h1>Use Callback</h1>
<button onClick={ () => unrelated() }>Unrelated!</button>
<h3>Unrelated: { unrelatedCount }</h3>
<Customers customers={customers} addCustomer={addCustomer}/>
</div>
);
}

export default App;

If we run this, apparently nothing strange happens, but if we take a look in the development console, we see that the customer component is rendered every time we click the Unrelated button and every time we add a new Customer.

So it the Customer component did an API call or something "expensive" this would execute every time.

2. Fix with the useCallback hook


import {useState, useCallback} from "react";
import Customers from "./components/Customers";

const App = () => {

const [count, setCount] = useState(2)
const [customers, setCustomers] = useState(["Klant #1"]);
const [unrelatedCount, setUnrelatedCount] = useState(0)

const addCustomer = useCallback( () => {
setCustomers((c) => [...c, `Customer #${count}`])
setCount( c => c + 1)
},[customers])

const unrelated = () => {
setUnrelatedCount( (c)=> c+1)
}

return (
<div className="App">
<h1>Use Callback</h1>
<button onClick={ () => unrelated() }>Unrelated!</button>
<h3>Unrelated: { unrelatedCount }</h3>
<Customers customers={customers} addCustomer={addCustomer}/>
</div>
);
}

export default App;

Breakdown

  • on line 11 - 14 we changed the addCustomer function to a useCallback function which depends on the customers state variable.
  • Check the console to see what's happening now