REACT useCallback

The useCallback hook in React is used to memoize functions so that they do not get recreated on every render. It ensures that a function remains the same (i.e., the same reference) across renders, as long as its dependencies haven’t changed. This is particularly useful when passing callbacks to child components, where unnecessary re-renders can be avoided.

When to Use useCallback

  • Performance Optimization: To prevent the recreation of functions, especially when passing them as props to child components.
  • Stable Function References: When a stable reference is needed for a function, such as for event handlers or callback dependencies.
  • Avoid Unnecessary Re-Renders: To reduce re-renders in child components that rely on a function prop.

Syntax

const memoizedCallback = useCallback(callbackFunction, [dependencies]);

Try It Now

  • callbackFunction: The function to memoize.
  • dependencies: An array of values the function depends on. The function is recreated only when one of these dependencies changes.

Basic Example: Without useCallback

import React, { useState } from "react";

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

    const increment = () => {
        setCount((prevCount) => prevCount + 1);
    };

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

export default Counter;

Try It Now

In this example, the increment function is recreated every time the Counter component re-renders. This is fine for simple components, but in more complex scenarios, this can lead to performance issues.

Using useCallback

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

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

    const increment = useCallback(() => {
        setCount((prevCount) => prevCount + 1);
    }, []); // No dependencies, so the function reference remains stable.

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

export default Counter;

Try It Now

In this version, the increment function is memoized using useCallback, meaning it won’t be recreated on every render.

Passing Callback to Child Component

Without useCallback (Causes Re-Renders):

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

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

    const increment = () => {
        setCount((prevCount) => prevCount + 1);
    };

    return (
        <div>
            <h1>Parent Count: {count}</h1>
            <Child increment={increment} />
        </div>
    );
}

function Child({ increment }) {
    console.log("Child rendered");
    return <button onClick={increment}>Increment from Child</button>;
}

export default Parent;

Try It Now

Every time the Parent component renders, the increment function is recreated, causing the Child component to re-render even if its props haven’t changed.

With useCallback (Avoids Unnecessary Re-Renders):

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

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

    const increment = useCallback(() => {
        setCount((prevCount) => prevCount + 1);
    }, []); // The function reference remains the same.

    return (
        <div>
            <h1>Parent Count: {count}</h1>
            <Child increment={increment} />
        </div>
    );
}

function Child({ increment }) {
    console.log("Child rendered");
    return <button onClick={increment}>Increment from Child</button>;
}

export default Parent;

Try It Now

In this version, the Child component doesn’t re-render unnecessarily because the increment function reference remains stable.

Real-World Example: useCallback with React.memo

import React, { useState, useCallback, memo } from "react";

const Button = memo(({ handleClick, label }) => {
    console.log(`${label} button rendered`);
    return <button onClick={handleClick}>{label}</button>;
});

function App() {
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);

    const incrementCount1 = useCallback(() => {
        setCount1((prev) => prev + 1);
    }, []);

    const incrementCount2 = useCallback(() => {
        setCount2((prev) => prev + 1);
    }, []);

    return (
        <div>
            <h1>Count 1: {count1}</h1>
            <h1>Count 2: {count2}</h1>
            <Button handleClick={incrementCount1} label="Increment Count 1" />
            <Button handleClick={incrementCount2} label="Increment Count 2" />
        </div>
    );
}

export default App;

Try It Now

Key Points in This Example

  1. React.memo: Memoizes the Button component, ensuring it only re-renders if its props change.
  2. useCallback: Memoizes the incrementCount1 and incrementCount2 functions, ensuring their references don’t change, preventing unnecessary re-renders of Button.

Common Mistakes

  1. Using useCallback Everywhere: Only use useCallback when a memoized function provides measurable performance benefits.
  2. Ignoring Dependencies: Always include all necessary dependencies in the dependency array. Missing dependencies can lead to bugs.
  3. Over-Optimizing: Premature optimization with useCallback can add unnecessary complexity.

Key Takeaways

  • useCallback is used for memoizing functions to avoid unnecessary recreations.
  • It’s most effective when combined with React.memo to optimize child components.
  • Always carefully consider dependencies to avoid bugs or stale closures.
  • Use it judiciously to improve performance in complex React applications.

 

By using useCallback effectively, you can improve the performance and efficiency of your React applications.