How to Update useCallback Hook in React

This article explains how to properly update and manage the useCallback hook in React. You will learn how the dependency array triggers function updates, how to use functional state updates to prevent unnecessary recreation of the callback, and how to access the latest values using refs.

Understanding the Dependency Array

To update a useCallback hook, you must correctly configure its dependency array (the second argument passed to the hook). React memoizes the function definition and will only update (re-create) the function when one of the dependencies listed in the array changes.

import { useCallback, useState } from 'react';

function UserComponent() {
  const [userId, setUserId] = useState(1);

  // This callback updates only when userId changes
  const fetchUserData = useCallback(() => {
    fetch(`https://api.example.com/users/${userId}`)
      .then(res => res.json())
      .then(data => console.log(data));
  }, [userId]); 
}

If you omit a dependency that is used inside the function, the function will suffer from “stale closures,” meaning it will continue to reference the old values from when the function was first created.

Updating State Without Triggering Callback Updates

A common mistake is adding a state variable to the dependency array just to update that state. This causes the useCallback to recreate on every state change, defeating the purpose of memoization.

You can update state and avoid recreating the callback by using a functional state update:

import { useState, useCallback } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  // Bad: Recreates the function on every count change
  const incrementBad = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  // Good: Never recreates because the dependency array is empty
  const incrementGood = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []); 
}

Using Refs for Frequently Changing Values

If you need to access a rapidly changing value inside your callback but do not want the callback to update and trigger rerenders of child components, you can store the value in a useRef.

import { useState, useCallback, useRef, useEffect } from 'react';

function FormComponent({ onSubmit }) {
  const [text, setText] = useState("");
  const textRef = useRef(text);

  // Keep the ref updated with the latest state
  useEffect(() => {
    textRef.current = text;
  }, [text]);

  // Callback does not update when text changes, but always has the latest value
  const handleSubmit = useCallback(() => {
    onSubmit(textRef.current);
  }, [onSubmit]); 
}