Understanding useCallback, A memoized function

3 min read
Table of Contents

Let’s list some reasons why we even need a useCallback to begin with.

  1. Each re-render wouldn’t unnecessarily re-create the function.
  2. In case we actually need to re-evaluate the function, we pass it as a reactive dependency.

Avoid Unneccesary Re-renders

Lets take below code for example, the increment button increases the counter value by 1 and we avoided the re-renders on state change with our useCallback hook.

import { useCallback, useState } from "react"
const buttonStyle = {
padding: "8px 16px",
margin: "8px",
borderRadius: "6px",
backgroundColor: "#4CAF50",
} as const;
export default function Sample() {
const [counter, setCounter] = useState(0)
const increment = useCallback((e) => {
setCounter(x => x+1)
}, [])
return <div style={{height: "400px", width: "600px", margin: "auto", marginTop: "100px"}}>
Current Value: {counter}
<div>
<button
onMouseDown={(e) => {
e.currentTarget.style.transform = "scale(0.95)";
e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
}}
onMouseUp={(e) => {
e.currentTarget.style.transform = "scale(1)";
e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
}}
style={buttonStyle} onClick={increment}> Increment </button>
</div>
</div>
}

useCallback without reactive dependency
Basic usage of useCallback in action
Now lets imagine there is some state update in your application, as a result your increment button should start incrementing it by some other number, this is a good scenario to show full use-case of useCallback

Re-evaluating the function on state change based on some state update

Lets update our code to support a use-case where our the state of our component gets changed after 5 seconds. After 5 seconds have elapsed, we should increment our numbers by 2.

In real applications, this state could be an API response or some other event triggered from the other component in the DOM.

import { useCallback, useEffect, useState } from "react"
const buttonStyle = {
padding: "8px 16px",
margin: "8px",
borderRadius: "6px",
backgroundColor: "#4CAF50",
} as const;
export default function Sample() {
const [counter, setCounter] = useState(0)
const [incrementValue, setIncrementValue] = useState(1)
const increment = useCallback((e) => {
setCounter(x => x+incrementValue)
}, [incrementValue])
useEffect(() => {
const id = setTimeout(() => {
setIncrementValue(2)
console.log("Increment set to 2")
}, 5000)
return () => clearTimeout(id)
}, [])
return <div style={{height: "400px", width: "600px", margin: "auto", marginTop: "100px"}}>
Current Value: {counter}
<div>
<button
onMouseDown={(e) => {
e.currentTarget.style.transform = "scale(0.95)";
e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
}}
onMouseUp={(e) => {
e.currentTarget.style.transform = "scale(1)";
e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
}}
style={buttonStyle} onClick={increment}> Increment {incrementValue > 1 && `+${incrementValue}`} </button>
</div>
</div>
}

useCallback with reactive dependency
useCallback with reactive dependency

Let’s understand what is being done here.

  1. We first introduced a new state variable, incrementValue
  2. Every time incrementValue gets changed, we want our useCallback to re-evaluate the function with incrementValue
  3. We add a 5 seconds timer to change the incrementValue to 2 and after 5 seconds, the increments will happen with 2.

Recreating “useCallback” with “useMemo”

Now that we now we simply keep a memoized version of a function, We can actually re-create useCallback with useMemo, with small changes you get below code with the exact same behavior.

import { useCallback, useEffect, useMemo, useState } from "react"
const buttonStyle = {
padding: "8px 16px",
margin: "8px",
borderRadius: "6px",
backgroundColor: "#4CAF50",
} as const;
export default function Sample() {
const [counter, setCounter] = useState(0)
const [incrementValue, setIncrementValue] = useState(1)
const increment = useCallback((e) => {
const increment = useMemo(() => function(e) {
setCounter(x => x+incrementValue)
}, [incrementValue])
useEffect(() => {
const id = setTimeout(() => {
setIncrementValue(2)
console.log("Increment set to 2")
}, 5000)
return () => clearTimeout(id)
}, [])
return <div style={{height: "400px", width: "600px", margin: "auto", marginTop: "100px"}}>
Current Value: {counter}
<div>
<button
onMouseDown={(e) => {
e.currentTarget.style.transform = "scale(0.95)";
e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
}}
onMouseUp={(e) => {
e.currentTarget.style.transform = "scale(1)";
e.currentTarget.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
}}
style={buttonStyle} onClick={increment}> Increment {incrementValue > 1 && `+${incrementValue}`} </button>
</div>
</div>
}
My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts