React useCallback

Optimizing function references in React

⚡ What is useCallback?

useCallback is a React Hook that returns a memoized callback function. It prevents unnecessary re-creation of functions, improving performance when passing callbacks to optimized child components.


import { useCallback } from 'react';

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

useCallback Concepts

🔒

Memoization

Caches function between re-renders

const memoizedFn = useCallback(fn, deps);
📋

Dependencies

Array of values function depends on

useCallback(fn, [count, name]);
🚀

Performance

Prevents unnecessary child re-renders

<Child onClick={memoizedFn} />
🔄

Referential Equality

Same function reference across renders

// Same reference if deps unchanged

🔹 Basic useCallback Example

Without useCallback, functions are recreated on every render. This example shows how useCallback preserves function identity between renders when dependencies don't change.

import { useState, useCallback } from 'react';

function SearchBar() {
  const [query, setQuery] = useState('');

  // This function is recreated only when query changes
  const handleSearch = useCallback(() => {
    console.log('Searching for:', query);
    // Perform search logic here
  }, [query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}

Output:

🔹 Preventing Child Re-renders

useCallback is most useful with React.memo. When you pass a callback to a memoized child component, useCallback ensures the child doesn't re-render unnecessarily.

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

// Memoized child component
const Button = memo(({ onClick, children }) => {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
});

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

  // Without useCallback, Button re-renders when text changes
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []); // No dependencies - function never changes

  return (
    <div>
      <p>Count: {count}</p>
      <Button onClick={increment}>Increment</Button>
      
      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Type here..."
      />
      <p>Button won't re-render when typing!</p>
    </div>
  );
}

Output:

Count: 0


Button won't re-render when typing!

🔹 useCallback with Dependencies

When your callback uses props or state, include them in the dependency array. The function will be recreated only when these dependencies change.

import { useState, useCallback } from 'react';

function ItemList() {
  const [items, setItems] = useState(['Apple', 'Banana']);
  const [filter, setFilter] = useState('');

  // Recreated only when filter changes
  const filterItems = useCallback(() => {
    return items.filter(item =>
      item.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]); // Dependencies: items and filter

  const filteredItems = filterItems();

  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder="Filter items..."
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

🔹 When to Use useCallback

useCallback is not always necessary. Use it strategically for performance optimization:

✅ Use useCallback when:

  • Passing to memoized components: Child uses React.memo
  • Dependency in other hooks: Function used in useEffect, useMemo
  • Expensive child components: Preventing unnecessary re-renders matters
  • Event handlers in lists: Many items with individual handlers

❌ Don't use useCallback when:

  • Simple components: No performance issues observed
  • Not passed as props: Function only used locally
  • Premature optimization: No measurable benefit
  • Every function: Adds complexity without need

🔹 useCallback vs Regular Function

Compare the difference between regular functions and useCallback:

import { useState, useCallback } from 'react';

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

  // ❌ Recreated on every render
  const regularFunction = () => {
    console.log('Regular function');
  };

  // ✅ Recreated only when count changes
  const memoizedFunction = useCallback(() => {
    console.log('Memoized function', count);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={regularFunction}>Regular</button>
      <button onClick={memoizedFunction}>Memoized</button>
    </div>
  );
}

🧠 Test Your Knowledge

What is the main purpose of useCallback?