Tell us about the useCallback hook, how it works, where it's applied and for what purpose?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Medium
#React #Hooks

Brief Answer

useCallback is a React hook that allows you to memoize (cache) functions between renders. It prevents creating new function instances on every render, which helps optimize performance and avoid unnecessary re-renders of child components.

Syntax:

const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);

Key features:

  • Optimizes performance by caching function instances
  • Prevents unnecessary re-renders of child components
  • Works with React.memo for efficient component optimization
  • Takes a function and dependency array as parameters

Usage example:

function ParentComponent({ data }) {
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);
  
  return <ChildComponent onClick={handleClick} />;
}

Full Answer

The useCallback hook is one of React’s built-in hooks designed to optimize performance by memoizing function instances. It helps avoid creating new function instances on every component render, which is especially important when passing callbacks to optimized child components. 🚀

How useCallback works?

useCallback takes two parameters:

  1. Function — the function to memoize
  2. Dependencies array — when these change, a new function instance is created
import React, { useCallback, useState } from 'react';
 
function Component() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // Function is created only once
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

When component renders:

  1. React checks dependencies array
  2. If dependencies haven’t changed, returns cached function
  3. If dependencies changed, creates new function and caches it

Main useCallback applications

1. Optimizing child component re-renders

function Parent() {
  const [count, setCount] = useState(0);
  
  // Without useCallback - new function on every render
  // const increment = () => setCount(prev => prev + 1);
  
  // With useCallback - stable function reference
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);
  
  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent onIncrement={increment} />
    </div>
  );
}
 
// Child component optimized with React.memo
const ChildComponent = React.memo(function ChildComponent({ onIncrement }) {
  console.log('ChildComponent rendered');
  return <button onClick={onIncrement}>Increment</button>;
});

2. Working with useEffect dependencies

function DataFetcher({ userId }) {
  const [data, setData] = useState(null);
  
  const fetchUserData = useCallback(async () => {
    const response = await fetch(`/api/users/${userId}`);
    const userData = await response.json();
    setData(userData);
  }, [userId]);
  
  // useEffect won't recreate subscription when fetchUserData changes
  useEffect(() => {
    fetchUserData();
  }, [fetchUserData]);
  
  return <div>{data ? data.name : 'Loading...'}</div>;
}

3. Event handlers in lists

function TodoList({ todos, onToggle }) {
  return (
    <ul>
      {todos.map(todo => (
        <TodoItem 
          key={todo.id} 
          todo={todo} 
          onToggle={useCallback(() => onToggle(todo.id), [onToggle, todo.id])}
        />
      ))}
    </ul>
  );
}
 
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
  return (
    <li>
      <label>
        <input 
          type="checkbox" 
          checked={todo.completed} 
          onChange={onToggle} 
        />
        {todo.text}
      </label>
    </li>
  );
});

When to use useCallback

Use useCallback when:

  1. Passing callbacks to optimized components — when using React.memo, shouldComponentUpdate
  2. Functions in dependency arrays — for useEffect, useMemo, useCallback dependencies
  3. Expensive function creation — when function creation is computationally expensive
  4. Working with large lists — to prevent re-renders of list items
function ExpensiveComponent({ items, onUpdate }) {
  // Stable callback for optimized child components
  const handleUpdate = useCallback((id, value) => {
    onUpdate(id, value);
  }, [onUpdate]);
  
  return (
    <div>
      {items.map(item => (
        <MemoizedItem 
          key={item.id} 
          item={item} 
          onUpdate={handleUpdate} 
        />
      ))}
    </div>
  );
}
 
const MemoizedItem = React.memo(function MemoizedItem({ item, onUpdate }) {
  return (
    <div>
      <span>{item.name}</span>
      <button onClick={() => onUpdate(item.id, 'new value')}>
        Update
      </button>
    </div>
  );
});

When to avoid useCallback

Avoid useCallback when:

  1. Simple components — without React.memo or shouldComponentUpdate
  2. Frequently changing dependencies — if dependencies change often, cache will constantly reset
  3. Simple event handlers — basic click handlers in small components
  4. When performance is not critical — premature optimization can hurt readability
// ❌ Don't use useCallback unnecessarily
function SimpleComponent() {
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []); // Overkill for simple component
  
  return <button onClick={handleClick}>Click me</button>;
}
 
// ✅ Just use a regular function
function SimpleComponent() {
  const handleClick = () => {
    console.log('Clicked');
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

Common mistakes

1. Using useCallback unnecessarily

// ❌ Overuse
function Component() {
  const handleSubmit = useCallback(() => {
    console.log('Form submitted');
  }, []); // Not passed to optimized components
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" />
      <button type="submit">Submit</button>
    </form>
  );
}
 
// ✅ Just use a regular function
function Component() {
  const handleSubmit = () => {
    console.log('Form submitted');
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input type="text" />
      <button type="submit">Submit</button>
    </form>
  );
}

2. Incorrect dependencies array

// ❌ Error: missing dependency
function Component({ userId }) {
  const fetchUser = useCallback(async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }, []); // userId not in dependencies!
  
  return <UserDisplay fetchUser={fetchUser} />;
}
 
// ✅ Correctly: all dependencies specified
function Component({ userId }) {
  const fetchUser = useCallback(async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }, [userId]); // userId in dependencies
  
  return <UserDisplay fetchUser={fetchUser} />;
}

3. Empty dependencies when they shouldn’t be

// ❌ Error: stale closure
function Component({ userId }) {
  const fetchUser = useCallback(async () => {
    // userId will always be the initial value
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }, []); // Empty dependencies
  
  return <UserDisplay fetchUser={fetchUser} />;
}
 
// ✅ Correctly: include changing dependencies
function Component({ userId }) {
  const fetchUser = useCallback(async () => {
    // userId will be current value
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }, [userId]); // Include userId
  
  return <UserDisplay fetchUser={fetchUser} />;
}

Summary

useCallback is a React hook for:

  • Memoizing function instances between renders
  • Optimizing component performance
  • Preventing unnecessary re-renders of child components
  • Working efficiently with React.memo and useEffect

When to use:

  • Passing callbacks to optimized child components
  • Functions in dependency arrays of other hooks
  • Working with large lists or expensive function creation
  • When child components are optimized with React.memo

When to avoid:

  • Simple components without optimization
  • Frequently changing dependencies
  • Basic event handlers in small components
  • When performance optimization is not needed

Best practices:

  • Use only when really needed for optimization
  • Always specify correct dependencies array
  • Don’t use for simple components without optimization
  • Combine with React.memo for maximum effectiveness

useCallback is a powerful optimization tool, but it shouldn’t be used mindlessly. First identify real performance issues, then apply useCallback to solve them. 🚀


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪