Understanding useCallback

I hope you know what is useMemo and understood that it caches the values between re-renders. useCallback is similar to this and it caches a function instead of values. Let’s dive into the details of useCallback.

What is useCallback?

useCallback is a React Hook that helps you memoize a function definition, ensuring it remains the same across re-renders.

Although useCallback shares a similar syntax with useMemo, there’s an important distinction between the two. useMemo executes a function and returns its result whenever its dependencies change. On the other hand, useCallback doesn’t execute the function—instead, it returns a memoized version of the function itself.

This means React will maintain the same function reference as long as the dependencies don’t change, preserving referential equality. It’s worth noting that React doesn’t automatically call the function—it’s entirely up to you to decide when and how to use it.

Syntax:

const cachedFn = useCallback(function, dependencies)

Parameters

Here’s an improved version of your text with a polished tone:

  • function: This is the function you want to memoize. It can accept any arguments and return any value. During the initial render, React will simply return (not call) your function. On subsequent renders, React will return the same function reference as long as the dependencies haven’t changed. If the dependencies do change, React will use the latest version of your function from the current render and update its cache for future use.

  • dependencies: This is a list of all reactive values used within your function. Reactive values include props, state, and any variables or functions defined inside your component’s body. If you don’t provide a dependencies array, React will always return the cached function and won’t update it, even if the reactive values inside the function change. This can lead to stale data if not handled carefully.

Usage

Below are the use cases of useCallback:

  • Skipping re-rendering of components.
  • Updating state from a memoized callback.
  • Preventing an Effect from firing too often.
  • Optimizing a custom Hook.

Example:

To understand this concept better, let’s explore an example where we prevent unnecessary re-renders of components.

In the code below, every time the button to increment the count is clicked, the DemoOutput component also re-renders. By default, when a component re-renders, React re-renders all of its children recursively. While this behavior is acceptable for lightweight components, it can negatively impact the app’s performance if the re-rendering process takes significant time.

Example without useCallback

// Counter.jsx
import { useState } from 'react';
import DemoOutput from './DemoOutput';

const Counter = () => {
  const [count, setCount] = useState(0);
  const [show, setShow] = useState(false);

  const showOutput = () => {
    setShow(!show);
  };

  return (
    <div className="red-box">
      <h3>Without useCallback</h3>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment count</button>
      <DemoOutput show={show} showOutput={showOutput} />
    </div>
  );
};

export default Counter;
// DemoOutput.jsx
import React from 'react';

const DemoOutput = (props) => {
  console.log('DemoOutput rendered.');
  return (
    <p>
      <button onClick={props.showOutput}>Show Demo</button>
      <br />
      {props.show && 'Open the console and click the increment button.'}
    </p>
  );
};

export default React.memo(DemoOutput);

In this implementation, every click on the Increment count button triggers a re-render of the DemoOutput component. This happens because the showOutput function is re-created on each render, causing React to treat it as a new reference and re-render the memoized DemoOutput component. You can verify this by observing the console logs.

Optimizing performance with useCallback

We can improve the performance by using the useCallback hook to memoize the showOutput function. By wrapping the function with useCallback, React ensures the function maintains the same reference across renders unless its dependencies change. This prevents the unnecessary re-rendering of the DemoOutput component.

Check the console logs to see the difference in rendering behavior between the two examples.

Here’s the optimized code:

// Counter.jsx
import { useState, useCallback } from 'react';
import DemoOutput from './DemoOutput';

const Counter = () => {
  const [count, setCount] = useState(0);
  const [show, setShow] = useState(false);

  const showOutput = useCallback(() => {
    setShow(!show);
  }, [show]);

  return (
    <div className="green-box">
      <h3>With useCallback</h3>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment count</button>
      <DemoOutput show={show} showOutput={showOutput} />
    </div>
  );
};

export default Counter;

How it works

  • The showOutput function is now wrapped in useCallback with show as a dependency.
  • The function is re-created only when show changes, maintaining referential equality otherwise.
  • The React.memo-wrapped DemoOutput component will no longer re-render unnecessarily because its showOutput prop remains the same between renders.

Summary

useCallback is a React Hook that allows you to memoize a function, ensuring its reference remains unchanged across re-renders. This can significantly enhance performance by preventing unnecessary re-renders of child components that depend on the function.

Example repository

Check out the full working examples in the GitHub Repository.

References

useCallback

Read More