Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. React Dictionary
  3. useCallback()

useCallback()

Since: React 16.8(2019)

useCallback in React is a hook that memoizes a function reference to prevent unnecessary re-creation. As long as the dependent values do not change, the same function reference is returned, which prevents unnecessary re-rendering of child components.

Syntax

import { useCallback } from 'react';

const memoizedFn = useCallback(
  function() {
    // logic to execute
  },
  [dependencies]
);

Parameters and Return Value

ItemDescription
fn (1st argument)The callback function to memoize. A new reference is created on the initial render and whenever a value in the dependency array changes.
dependencies (2nd argument)An array of values that trigger re-creation of the function. Passing [] creates the function only once on mount. Omitting this argument re-creates the function on every render.
Return valueThe memoized callback function. The same function reference is returned as long as the dependency values do not change.

Sample Code

Basic: empty dependency array

Setter functions like setCount have a stable reference guaranteed by React, so they do not need to be included in the dependency array. Passing [] creates the function only once on the initial render.

import { useState, useCallback } from 'react';

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

  const handleIncrement = useCallback(function() {
    setCount(function(prev) { return prev + 1; });
  }, []);

  const handleDecrement = useCallback(function() {
    setCount(function(prev) { return prev - 1; });
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleIncrement}>+1</button>
      <button onClick={handleDecrement}>-1</button>
    </div>
  );
}

export default Counter;

Including values in the dependency array

Any state or props referenced inside the function must be included in the dependency array. The function is re-created only when those values change.

import { useState, useCallback } from 'react';

function SearchPanel() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  // query is referenced inside, so it must be in the dependency array
  const handleSearch = useCallback(function() {
    var members = ['Tsunemori Akane', 'Ginoza Nobuchika', 'Kogami Shinya'];
    var found = members.filter(function(name) {
      return name.toLowerCase().includes(query.toLowerCase());
    });
    setResults(found);
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={function(e) { setQuery(e.target.value); }}
        placeholder="Search by name..."
      />
      <button onClick={handleSearch}>Search</button>
      <ul>
        {results.map(function(name) {
          return <li key={name}>{name}</li>;
        })}
      </ul>
    </div>
  );
}

export default SearchPanel;

Combined with React.memo to prevent child re-renders

When passing a function as a prop to a child component wrapped in React.memo(), the child re-renders every time the function reference changes. Stabilizing the reference with useCallback() prevents that unnecessary re-rendering.

import { useState, useCallback, memo } from 'react';

// Child component wrapped with memo
// Will not re-render as long as the onClick reference does not change
var ActionButton = memo(function ActionButton({ onClick, label }) {
  console.log('ActionButton rendered:', label);
  return (
    <button onClick={onClick}>{label}</button>
  );
});

function InspectionPanel() {
  const [count, setCount] = useState(0);
  // This state is unrelated to useCallback
  // Changing it will not re-create the memoized functions
  const [note, setNote] = useState('');

  const handleApprove = useCallback(function() {
    setCount(function(prev) { return prev + 1; });
  }, []);

  const handleReject = useCallback(function() {
    setCount(function(prev) { return prev - 1; });
  }, []);

  return (
    <div>
      {/* Changing note will not re-render ActionButton */}
      <input
        value={note}
        onChange={function(e) { setNote(e.target.value); }}
        placeholder="Enter a note..."
      />
      <ActionButton onClick={handleApprove} label="Approve" />
      <ActionButton onClick={handleReject} label="Reject" />
      <p>Processed: {count}</p>
    </div>
  );
}

export default InspectionPanel;

Overview

useCallback is a hook that caches a function reference and re-creates it only when a value in the dependency array changes. Normally a function is rebuilt on every re-render of its component, but useCallback() keeps the same reference.

When passing a function as a prop to a child component wrapped in React.memo(), a changing function reference causes the child to re-render every time. Stabilizing the reference with useCallback() prevents that unnecessary re-rendering.

The rules for the dependency array are the same as for useEffect(). Any state or props referenced inside the function must be included. However, setter functions from useState() such as setCount have a stable reference guaranteed by React and do not need to be included.

To memoize a computed value rather than a function, use useMemo instead of useCallback. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

Related pages: useMemo / useState / useEffect

Common Mistakes

Missing a value in the dependency array

When a state or prop referenced inside the function is missing from the dependency array, the old value gets trapped in the closure and continues to be used. As a result, the function executes with stale data that does not reflect the latest state.

stale_closure_ng.jsx
import { useState, useCallback } from 'react';

function Form() {
  const [name, setName] = useState('');

  // name is referenced but the dependency array is empty,
  // so name is always the initial value ''
  const handleSubmit = useCallback(function() {
    console.log('Submit:', name); // always logs ''
  }, []); // name is missing

  return (
    <div>
      <input value={name} onChange={function(e) { setName(e.target.value); }} />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}

After fix:

stale_closure_ok.jsx
import { useState, useCallback } from 'react';

function Form() {
  const [name, setName] = useState('');

  // Including name in the dependency array ensures the latest value is used
  const handleSubmit = useCallback(function() {
    console.log('Submit:', name);
  }, [name]);

  return (
    <div>
      <input value={name} onChange={function(e) { setName(e.target.value); }} />
      <button onClick={handleSubmit}>Submit</button>
    </div>
  );
}

Using useCallback without React.memo has no effect

useCallback stabilizes a function reference, but the benefit only applies to components wrapped in React.memo or cases where a function is listed as a dependency of useEffect. Without React.memo, a child component re-renders on every parent render regardless of props, so using useCallback does not reduce re-renders.

no_memo_ng.jsx
import { useState, useCallback } from 'react';

// Child component not wrapped with memo
// Re-renders whenever the parent re-renders, regardless of props
function SubmitButton({ onClick }) {
  console.log('SubmitButton rendered');
  return <button onClick={onClick}>Submit</button>;
}

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

  // Stabilizing the reference with useCallback does not prevent SubmitButton from re-rendering
  const handleSubmit = useCallback(function() {
    console.log('Submit');
  }, []);

  return (
    <div>
      <button onClick={function() { setCount(function(c) { return c + 1; }); }}>
        Count: {count}
      </button>
      <SubmitButton onClick={handleSubmit} />
    </div>
  );
}

After fix: wrap the child component with memo.

no_memo_ok.jsx
import { useState, useCallback, memo } from 'react';

// Wrapping with memo makes useCallback effective
var SubmitButton = memo(function SubmitButton({ onClick }) {
  console.log('SubmitButton rendered');
  return <button onClick={onClick}>Submit</button>;
});

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

  const handleSubmit = useCallback(function() {
    console.log('Submit');
  }, []);

  return (
    <div>
      <button onClick={function() { setCount(function(c) { return c + 1; }); }}>
        Count: {count}
      </button>
      {/* SubmitButton will not re-render when count changes */}
      <SubmitButton onClick={handleSubmit} />
    </div>
  );
}

Wrapping the memoized function in an inline function negates memoization

When passing a memoized function to a child component, wrapping it in an additional inline function inside JSX creates a new function reference on every render. This negates the effect of useCallback.

inline_fn_ng.jsx
import { useState, useCallback, memo } from 'react';

var ActionButton = memo(function ActionButton({ onClick, label }) {
  console.log('ActionButton rendered:', label);
  return <button onClick={onClick}>{label}</button>;
});

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

  const handleApprove = useCallback(function() {
    setCount(function(prev) { return prev + 1; });
  }, []);

  return (
    <div>
      {/* An inline wrapper creates a new reference every render,
          causing ActionButton to re-render */}
      <ActionButton
        onClick={function() { handleApprove(); }}
        label="Approve"
      />
      <p>Count: {count}</p>
    </div>
  );
}

After fix: pass the memoized function directly.

inline_fn_ok.jsx
import { useState, useCallback, memo } from 'react';

var ActionButton = memo(function ActionButton({ onClick, label }) {
  console.log('ActionButton rendered:', label);
  return <button onClick={onClick}>{label}</button>;
});

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

  const handleApprove = useCallback(function() {
    setCount(function(prev) { return prev + 1; });
  }, []);

  return (
    <div>
      {/* Pass the memoized handleApprove directly */}
      <ActionButton onClick={handleApprove} label="Approve" />
      <p>Count: {count}</p>
    </div>
  );
}

If you find any errors or copyright issues, please .