useCallback Hook

Learn how useCallback in React helps memoize functions, preventing unnecessary re-creation and optimizing performance.

Loading...
useCallback Hook

In React applications, unnecessary function re-creation can lead to performance issues, especially when passing functions as props to child components. This is where the useCallback in React comes in.

What is useCallback?

useCallback is a React Hook that memoizes functions. This stores the reference of the function so that you can avoid creating a new function on each render.

This improves the performance, especially when you need to pass functions from parent components to child components.

If a function is creating unnecessary, then it’s a best practice to use the useCallback hook.


Basic Syntax of useCallback

Before using useCallback, you need to import it from React:

import { useCallback } from "react";

Syntax:

const memoizedFunction = useCallback(() => {
  // Function logic
}, [dependencies]);

Here,

  • useCallback accepts a function that gets memoized.
  • The function will be re-created only when the dependencies change.
  • If dependencies are the same, then the same function reference will be returned.

Function Re-Creation Without useCallback

If you don’t use the useCallback hook, then on each render, a new function will be created, which causes unnecessary renders.

import { useState } from "react";
 
function ChildComponent({ handleClick }) {
  console.log("Child re-rendered!"); // The child component re-render on each render
 
  return <button onClick={handleClick}>Click Me</button>;
}
 
export default function App() {
  const [count, setCount] = useState(0);
 
  const handleClick = () => {
    console.log("Button clicked!");
  };
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent handleClick={handleClick} />
    </div>
  );
}

How does it work?

Child Component (ChildComponent):

  • Accepts handleClick as a prop.
  • Logs “Child re-rendered!” on every render.
  • Renders a button that calls handleClick when clicked.

Parent Component (App):

  • Uses useState to manage count.
  • Defines the handleClick function (logs “Button clicked!”).
  • Renders: - A paragraph showing count. - A button to increment the count. - ChildComponent, passing handleClick as a prop.

Re-render Issue:

  • On every App re-render (when count updates), handleClick is recreated.
  • Since a new function reference is passed, ChildComponent also re-renders unnecessarily.

Function Re-Creation Avoided With useCallback

You can avoid the unnecessary function re-creation using useCallback.

import { useState, useCallback } from "react";
 
function ChildComponent({ handleClick }) {
  console.log("Child re-rendered!"); // This will only re-render when function changes
 
  return <button onClick={handleClick}>Click Me</button>;
}
 
export default function App() {
  const [count, setCount] = useState(0);
 
  const handleClick = useCallback(() => {
    console.log("Button clicked!");
  }, []); // Function reference will not change
 
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent handleClick={handleClick} />
    </div>
  );
}

How does it work?

Child Component (ChildComponent):

  • Accepts handleClick as a prop.
  • Logs “Child re-rendered!” on render.
  • Renders a button that calls handleClick when clicked.

Parent Component (App):

  • Uses useState to manage count.
  • Wraps handleClick with useCallback (dependency array [ ] ensures function reference remains stable).
  • Renders:
    • A paragraph showing count.
    • A button to increment the count.
    • ChildComponent, passing the memoized handleClick.

Optimization with useCallback:

  • Now, the handleClick reference does not change on re-renders.
  • ChildComponent only re-renders when necessary, reducing unnecessary renders.

Event Handlers in useCallback

If an event handler is repeatedly re-created, then you can optimize this by using the useCallback.

Without useCallback (Unoptimized Code)

import { useState } from "react";
 
export default function App() {
  const [count, setCount] = useState(0);
 
  const increment = () => setCount(count + 1); // New function is creating on each render
 
  return <button onClick={increment}>Count: {count}</button>;
}

How does it work?

Defines increment Function:

  • Increments count by 1.
  • Created inside the component, so a new function is created on every render.

Returns a Button:

  • Displays count.
  • Calls increment on click.

Issue: Unnecessary function re-creation

  • On each re-render, a new instance of increment is created.
  • This can cause performance issues if passed as a prop to child components.

With useCallback (Optimized Code)

import { useState, useCallback } from "react";
 
export default function App() {
  const [count, setCount] = useState(0);
 
  const increment = useCallback(() => setCount(count + 1), [count]); // Memoized function
 
  return <button onClick={increment}>Count: {count}</button>;
}

How does it work?

Defines increment Function:

  • Wrapped in useCallback with [count] as a dependency.
  • Memoizes increment, so a new function is created only when count changes.

Returns a Button:

  • Displays count.
  • Calls increment on click.

Why is this better than the previous version?

  • Prevents unnecessary function re-creation on each render.
  • Useful when passing increment as a prop to child components.

useCallback in API Calls & Dependencies

If a function calls an API or depends on a state, then you can optimize that by using the useCallback.

import { useState, useCallback } from "react";
 
export default function App() {
  const [query, setQuery] = useState("");
 
  const fetchData = useCallback(() => {
    console.log("Fetching data for:", query);
    // API call logic
  }, [query]); // Function will recreate only when the `query` changes
 
  return (
    <div>
      <input onChange={(e) => setQuery(e.target.value)} />
      <button onClick={fetchData}>Search</button>
    </div>
  );
}

How does it work?

Defines fetchData Function:

  • Logs “Fetching data for:” along with the current query.
  • Wrapped in useCallback with [query] as a dependency.
  • This ensures that fetchData is only recreated when the query changes.

Returns an Input Field & Button:

  • The input updates the query on every change.
  • The button triggers fetchData to “fetch data” for the current query.

Optimization Benefit:

  • Prevents unnecessary re-creation of fetchData unless query changes.
  • Useful when passing fetchData to child components to avoid unnecessary re-renders.

What is the difference between useCallback and useMemo?

Feature useCallback useMemo
Purpose Function memoization Value memoization
Returns Memoized function reference Memoized computed value
Use Case When unnecessary function recreation happening When expensive calculation is happening repeatedly

In simple:

  • If you need to memoize a function, use useCallback.
  • If you need to memoize a computed value or expensive calculation, use useMemo.