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
memoHigher Order component to wrap things up. - line 24: it wraps the
Customersin thismemofunction so React knows how to handle it. - line 19: the
addCustomerfunction is passed fromApp
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
addCustomerfunction to auseCallbackfunction which depends on thecustomersstate variable. - Check the console to see what's happening now