Discovering React’s useCallback
There are several React hooks, and recently, while studying, I found that this hook deserves more of my attention.
But before diving into what useCallback
actually is, let’s start with a preliminary question: What exactly is a hook?
What is a hook?
Informally, hooks can be thought of as the “components” of business logic. When you need to execute the same logic in multiple places, you can isolate that logic in a hook and call it from any part of your system. This concept also applies to React’s native hooks.
Now that we understand that hooks are encapsulated logics meant for reuse, we can move on to the second topic necessary to understand useCallback
: React’s rendering process.
How does React.js render components?
React introduced the component-based architecture pattern in a very efficient way, where each part of your interface can be isolated and reused within the system. This significantly improved the organization and maintenance of web systems. To render these components, they need to be called within a parent component that functions as your page.
So far, this setup isn’t problematic, but it still doesn’t solve the performance issue, as every state change requires updating the entire page. There are other approaches to address this issue, depending on the context, but today we’ll focus on useCallback
.
So, what does useCallback
do?
Basically, it’s a hook that caches your function, preventing it from being recreated unnecessarily. It will only be executed again if one of its dependencies changes (similar to useEffect
).
Example with code
Let’s create a counter where the user can click a button to increment a value and also increase the interval step.
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onIncrement }) => {
console.log("ChildComponent rendered");
return (
<div>
<button onClick={onIncrement}>Increment in Child</button>
</div>
);
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
const [incrementStep, setIncrementStep] = useState(1);
const incrementWithoutCallback = () => {
setCount((prevCount) => prevCount + incrementStep);
};
console.log("ParentComponent rendered");
return (
<div>
<h1>Counter: {count}</h1>
<h2>Without useCallback</h2>
<ChildComponent onIncrement={incrementWithoutCallback} />
<button onClick={() => setOtherState(!otherState)}>
Toggle Other State
</button>
<button onClick={() => setIncrementStep((prev) => prev + 1)}>
Increase Increment Step
</button>
</div>
);
}
export default ParentComponent;
In the example above, the child component is rendered each time there is a change in the parent component.
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onIncrement }) => {
console.log("ChildComponent rendered");
return (
<div>
<button onClick={onIncrement}>Increment in Child</button>
</div>
);
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
const [incrementStep, setIncrementStep] = useState(1);
const incrementWithCallback = useCallback(() => {
setCount((prevCount) => prevCount + incrementStep);
}, [incrementStep]);
console.log("ParentComponent rendered");
return (
<div>
<h1>Counter: {count}</h1>
<h2>With useCallback</h2>
<ChildComponent onIncrement={incrementWithCallback} />
<button onClick={() => setOtherState(!otherState)}>
Toggle Other State
</button>
<button onClick={() => setIncrementStep((prev) => prev + 1)}>
Increase Increment Step
</button>
</div>
);
}
export default ParentComponent;
When we use useCallback
, the child component will only re-render when the dependency changes.
I hope this text has helped you better understand how useCallback
works.
Hi, my name is Marcelle, and I’m a web developer. :)
I’ve been working in tech since 2016, and I’m passionate about what I do. That’s why I’m always studying and sharing my study notes in the form of articles.