[React.js] - Part 14. React Hooks - useCallback Hook

Ace Lennox
The useCallback
Hook in React helps improve performance by memoizing callback functions. Memoization is like caching: it ensures that functions are not recreated unnecessarily during renders. This guide will explain how to use the useCallback
Hook effectively, with examples that are easier to understand and implement.
What is useCallback
?
The useCallback
Hook returns a memoized version of a callback function. The function is only recreated if one of its dependencies changes. This is particularly useful for optimizing performance in components that rely on functions as props, especially when used with React.memo
.
Key Points:
Memoized Functions: Prevents unnecessary recreation of functions during renders.
Performance Optimization: Reduces re-renders in child components.
Comparison to
useMemo
: WhileuseMemo
returns a memoized value,useCallback
returns a memoized function.
Problem: Unnecessary Re-renders
Here’s a simple app where a parent component manages a list of tasks:
Example: Without useCallback
index.js
import { useState } from "react";
import ReactDOM from "react-dom/client";
import Tasks from "./Tasks";
const App = () => {
const [count, setCount] = useState(0);
const [tasks, setTasks] = useState([]);
const increment = () => {
setCount((c) => c + 1);
};
const addTask = () => {
setTasks((prevTasks) => [...prevTasks, `Task ${prevTasks.length + 1}`]);
};
return (
<>
<Tasks tasks={tasks} addTask={addTask} />
<hr />
<div>
Count: {count}
<button onClick={increment}>Increment Count</button>
</div>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
Tasks.js
import { memo } from "react";
const Tasks = ({ tasks, addTask }) => {
console.log("Tasks component re-rendered!");
return (
<div>
<h2>Tasks</h2>
{tasks.map((task, index) => (
<p key={index}>{task}</p>
))}
<button onClick={addTask}>Add Task</button>
</div>
);
};
export default memo(Tasks);
Issue: Why Does Tasks
Re-render?
Even though we’re using React.memo
, the Tasks
component still re-renders when the count is incremented. This happens because the addTask
function is recreated every time the parent component re-renders. This breaks referential equality, causing Tasks
to re-render unnecessarily.
Solution: Using useCallback
The useCallback
Hook ensures that the addTask
function is not recreated unless its dependencies change.
Example: With useCallback
index.js
import { useState, useCallback } from "react";
import ReactDOM from "react-dom/client";
import Tasks from "./Tasks";
const App = () => {
const [count, setCount] = useState(0);
const [tasks, setTasks] = useState([]);
const increment = () => {
setCount((c) => c + 1);
};
const addTask = useCallback(() => {
setTasks((prevTasks) => [...prevTasks, `Task ${prevTasks.length + 1}`]);
}, [tasks]);
return (
<>
<Tasks tasks={tasks} addTask={addTask} />
<hr />
<div>
Count: {count}
<button onClick={increment}>Increment Count</button>
</div>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
Tasks.js
import { memo } from "react";
const Tasks = ({ tasks, addTask }) => {
console.log("Tasks component re-rendered!");
return (
<div>
<h2>Tasks</h2>
{tasks.map((task, index) => (
<p key={index}>{task}</p>
))}
<button onClick={addTask}>Add Task</button>
</div>
);
};
export default memo(Tasks);
Why Does This Work?
useCallback
memoizes theaddTask
function.Tasks
now re-renders only when thetasks
array changes, not when the parent component re-renders for other reasons (like updatingcount
).
Best Practices
Use with
React.memo
:useCallback
is most effective when combined withReact.memo
to prevent unnecessary re-renders in child components.Add Dependencies Carefully: Always include all variables referenced inside the callback function in the dependency array.
Optimize Only When Needed: Avoid overusing
useCallback
for trivial functions. It’s most useful in performance-critical scenarios.
Conclusion
The useCallback
Hook is a simple yet powerful tool for optimizing React applications. By ensuring that functions are only recreated when necessary, it helps reduce unnecessary renders and improves performance.
Experiment with the examples above to see how useCallback
can make your React applications more efficient and performant!
Source Code
The complete source code for this part is available on GitHub