React

React useCallback — Memoize Functions & Optimize Performance

Thirdy Gayares
18 min read

🎓 What You Will Learn

  • What useCallback does: Memoize callback functions to prevent unnecessary re-renders
  • When to use it: Optimize performance when passing functions to optimized child components
  • Dependency arrays: How dependencies control when functions are recreated
  • Performance impact: See real examples of how useCallback prevents child re-renders
  • Common patterns: useCallback with parameters, closures, and event handlers
  • When NOT to use it: Avoid premature optimization and unnecessary memoization
1

What is useCallback?

useCallback is a React Hook that memoizes a callback function. It returns the same function object between re-renders, unless its dependencies have changed.

Think of it as "Remember this function". Instead of creating a new function every render, React returns the same function object, preventing child components from re-rendering unnecessarily.

Memoization: Caching a value so the same object is returned without recalculating, reducing unnecessary work.
2

Basic Syntax

The syntax is straightforward:

example.jsx
import { useCallback } from 'react';

function Component() {
  // Memoize a callback function
  const handleClick = useCallback(() => {
    console.log('Button clicked!');
  }, []); // Empty dependency array

  return <button onClick={handleClick}>Click me</button>;
}

The second argument is a dependency array. When dependencies change, a new function is created. When nothing changes, the old function is reused.

3

Without vs With useCallback

See the difference between creating new functions every render vs memoizing with useCallback:

▶ Try it Yourself

Performance Comparison

Click the button. Every time the parent re-renders, a new function object is created, causing the child to re-render unnecessarily.

Count: 0 | Parent renders: 0

Child re-renders: 0

Key insight: useCallback only helps when child components use React.memo to prevent re-renders from props comparison.
4

Understanding Dependencies

The dependency array controls when a new function is created. If any dependency changes, useCallback creates a new function object.

Dependency ArrayFunction Behavior
[]Function created once, never updates (not recommended for most cases)
[variable]Function recreated when 'variable' changes
[var1, var2]Function recreated when either variable changes
No arrayCreates new function every render (defeats purpose)

▶ Try it Yourself

Dependency Array in Action

When dependencies change, useCallback creates a new function. Try changing the multiplier!

Count: 0

⚠️ Common mistake: Forgetting to include variables in the dependency array. If your function uses multiplier, include it in dependencies!
5

useCallback with Function Parameters

Memoized callbacks can accept parameters just like normal functions. This is useful for list items, dynamic handlers, or event callbacks.

▶ Try it Yourself

Adding and Removing Items

useCallback functions can accept parameters. Use them like normal functions.

  • React
  • JavaScript
  • TypeScript
✅ Best practice: Use useCallback with parameters for event handlers, filters, and dynamic operations.
6

Inline vs useCallback

Sometimes an inline function is perfectly fine. useCallback is useful for performance-critical scenarios, but not always necessary.

▶ Try it Yourself

When to Choose

Count: 0

New function created on each render
Rule of thumb: Use useCallback when passing functions to optimized child components (wrapped with React.memo).
7

When to Use useCallback

Use useCallback in these scenarios:

  • Child component wrapped with React.memo: Prevents unnecessary child re-renders
  • Functions as dependencies: When a callback is a dependency of another hook (useEffect)
  • Heavy computations: When creating the function is expensive
  • Optimized lists: When rendering large lists with callbacks
⚠️ Avoid premature optimization: Don't use useCallback everywhere. Measure performance first with React DevTools Profiler before optimizing.
8

Closures and useCallback

Memoized functions capture variables from their scope. This is called a closure. Be careful with stale closures!

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

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

  // WRONG: count is captured at function creation time
  const handleClickWrong = useCallback(() => {
    alert('Count is ' + count); // Always shows old count!
  }, []); // Missing count dependency!

  // RIGHT: count is included as dependency
  const handleClickRight = useCallback(() => {
    alert('Count is ' + count); // Shows current count
  }, [count]); // Include count

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={handleClickWrong}>Alert (Wrong)</button>
      <button onClick={handleClickRight}>Alert (Right)</button>
      <p>Count: {count}</p>
    </div>
  );
}
Stale closure: If your function uses state or props, include them in the dependency array to always have fresh values.
9

useCallback vs useMemo

Both are memoization hooks, but they memoize different things:

HookMemoizesUse Case
useCallbackFunctionsPrevent child re-renders, function dependencies
useMemoValuesExpensive computations, derived values
useCallback(() => 5, []).Is same as useMemo(() => () => 5, []).In practice, they solve different problems
callback-vs-memo.jsx
import { useState, useCallback, useMemo } from 'react';

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

  // useCallback: memoize a function
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  // useMemo: memoize a computed value
  const expensiveValue = useMemo(() => {
    return heavyComputation(count);
  }, [count]);

  return (
    <div>
      <button onClick={handleClick}>Increment</button>
      <p>Result: {expensiveValue}</p>
    </div>
  );
}
10

Best Practices & Tips

✅ DO:
  • Include all variables used in the callback as dependencies
  • Use with React.memo for optimized children
  • Measure performance impact with React DevTools Profiler
  • Use ESLint plugin eslint-plugin-react-hooks to catch dependency mistakes
⚠️ DON'T:
  • Use useCallback for simple inline handlers
  • Forget dependencies in the array
  • Optimize before measuring performance
  • Assume every child needs a memoized callback
11

What's Next

🚀 Continue learning:
  • useMemo: Memoize expensive computations
  • React.memo: Prevent component re-renders
  • Performance optimization: Profile and identify bottlenecks
  • useReducer: Manage complex state logic with callbacks

Now that you understand useCallback, you're ready to optimize React applications for better performance. Remember: measure first, optimize second!

About the Author

TG

Thirdy Gayares

Passionate developer creating custom solutions for everyone. I specialize in building user-friendly tools that solve real-world problems while maintaining the highest standards of security and privacy.