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

Ace Lennox
19 Jan 20253 minutes to read

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:

  1. Memoized Functions: Prevents unnecessary recreation of functions during renders.

  2. Performance Optimization: Reduces re-renders in child components.

  3. Comparison to useMemo: While useMemo 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 the addTask function.

  • Tasks now re-renders only when the tasks array changes, not when the parent component re-renders for other reasons (like updating count).


Best Practices

  1. Use with React.memo: useCallback is most effective when combined with React.memo to prevent unnecessary re-renders in child components.

  2. Add Dependencies Carefully: Always include all variables referenced inside the callback function in the dependency array.

  3. 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


Recent Posts

Latest Posts


logo

Explore insightful articles and tutorials on programming, web development, and mobile app creation. Dive into topics like ReactJS, Next.js, Android, iOS, and modern coding practices. Learn with practical examples and tips for building robust, responsive, and high-performing applications. Perfect for developers of all levels seeking to enhance their skills.

Social

© 2025. All rights reserved