REACT

React useCallback Hook: Syntax, Usage, and Examples

The useCallback hook in React returns a memoized version of a callback function. It helps prevent unnecessary re-renders by keeping the same function instance between renders unless its dependencies change.

How to Use useCallback in React

To use useCallback, you wrap your callback function inside the hook and pass in a dependency array. The syntax looks like this:

const memoizedCallback = useCallback(() => {
  // function logic
}, [dependency1, dependency2]);
  • useCallback: React hook that memoizes your function
  • Function: The actual callback you want to optimize
  • Dependency array: List of values that will trigger a new function instance if they change

Here's a basic example:

const handleClick = useCallback(() => {
  console.log('Button clicked');
}, []);

In this case, handleClick will not be recreated on re-renders unless one of the dependencies changes. Since the dependency array is empty, it remains the same for the lifetime of the component.

When to Use useCallback in React

The useCallback hook is a performance optimization tool. It’s not necessary in every component, but it becomes useful in the following situations:

Passing Stable Callbacks to Child Components

When you pass a function to a child component, React re-creates it every time the parent re-renders. If the child uses React.memo, this behavior can cause unnecessary renders. Using useCallback prevents this.

const onChange = useCallback((value) => {
  setState(value);
}, [setState]);

<MyInput onChange={onChange} />

In this example, onChange won't change unless setState changes. That keeps MyInput from re-rendering when it doesn’t need to.

Functions Inside useEffect

If you define a function inside a useEffect or pass a callback to it, the reference changes on each render unless you use useCallback. This can lead to infinite loops or missed updates.

useEffect(() => {
  const id = setInterval(() => {
    console.log('Tick');
  }, 1000);

  return () => clearInterval(id);
}, [/* dependencies */]);

If you used a callback inside useEffect, you’d want to memoize it with useCallback so the effect doesn’t re-run unnecessarily.

Optimizing Lists and Event Handlers

When rendering lists of items, each with an event handler like onClick, useCallback prevents each item from creating a new function instance, improving rendering performance.

const handleItemClick = useCallback((id) => {
  console.log(`Clicked item ${id}`);
}, []);

Using the same handleItemClick function reference helps React reuse DOM elements when diffing.

Examples of useCallback in React

Below are a few common scenarios where useCallback improves performance and predictability.

Example 1: Memoized Event Handler

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

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

  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <button onClick={increment}>
      Count: {count}
    </button>
  );
}

The increment function stays the same between renders, avoiding unnecessary re-binding.

Example 2: Preventing Unnecessary Child Renders

import React, { useState, useCallback } from 'react';
import Child from './Child';

function Parent() {
  const [text, setText] = useState('');

  const handleTextChange = useCallback((e) => {
    setText(e.target.value);
  }, []);

  return (
    <><input onChange={handleTextChange} value={text} />
      <Child onClick={handleTextChange} />
    </>
  );
}

If Child is wrapped in React.memo, it only re-renders if its props change. Since handleTextChange is stable, unnecessary re-renders are avoided.

Example 3: Async useCallback in React

You can use async functions with useCallback, but be aware they always return a Promise.

const fetchData = useCallback(async () => {
  const response = await fetch('/api/data');
  const result = await response.json();
  console.log(result);
}, []);

This pattern is useful when passing async callbacks to event handlers or effects that need stable references.

Learn More About useCallback in React

How to Use useCallback in React with Dependencies

The dependency array is essential. If you leave it out or include unstable values, useCallback won’t behave as expected. Only include values that are used inside the callback and that may change over time.

const handleSubmit = useCallback(() => {
  doSomething(userInput); // userInput is a dependency
}, [userInput]);

If userInput changes, a new version of handleSubmit will be created.

React useMemo vs useCallback

A common point of confusion is the difference between react usememo vs usecallback. Both are performance tools, but they serve different purposes.

  • React usecallback memoizes a function.
  • React usememo memoizes a computed value.

So if you’re optimizing a value, use useMemo:

const sortedList = useMemo(() => {
  return [...list].sort();
}, [list]);

If you’re optimizing a function, use useCallback:

const handleSort = useCallback(() => {
  setList([...list].sort());
}, [list]);

Both tools reduce unnecessary computation or re-renders, but they solve different problems.

Comparing Function Instances

React compares function references using shallow equality. Without useCallback, every new render creates a new function object, even if the logic doesn’t change. That’s why child components wrapped in React.memo can suffer from “prop changes” unless the callback is stabilized.

// BAD: New function every render
<Child onClick={() => setValue(5)} />

// GOOD: Stable function
const handleClick = useCallback(() => setValue(5), []);
<Child onClick={handleClick} />

Pitfalls and Misuse

useCallback is not always helpful. Overusing it can add complexity without real performance gain. Use it when:

  • The function is passed to a memoized child
  • The function is used in an effect dependency
  • The function is expensive to recreate

Avoid it for internal functions that aren’t memoized or passed around. The cost of using useCallback outweighs the benefits if there’s no re-render concern.

Integration with useEffect and useMemo

Sometimes you'll see a pattern where useCallback and useMemo are used together, especially in complex components.

const fetchData = useCallback(async () => {
  const res = await fetch('/api/data');
  return await res.json();
}, []);

const processedData = useMemo(() => {
  return heavyComputation(rawData);
}, [rawData]);

Both tools work well together. One stabilizes a function, the other stabilizes a value.

Handling Dependencies in Async useCallback React

When working with asynchronous code in async usecallback react, remember to manage stale closures. React doesn't cancel an ongoing async function if dependencies change, so be cautious with long-running operations.

const fetchData = useCallback(async () => {
  let canceled = false;

  const result = await fetchDataFromApi();

  if (!canceled) {
    setData(result);
  }

  return () => {
    canceled = true;
  };
}, []);

Adding cancellation logic helps prevent outdated data from overwriting current state.


React useCallback is a precision tool in your performance toolkit. It doesn’t make your app faster by default but helps avoid wasted renders and expensive operations when used correctly. With practice, you’ll develop a sense for when it's worth reaching for this hook—and when to just let React handle it.

Learn to Code in React for Free
Start learning now
button icon
To advance beyond this tutorial and learn React by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster