Code Your Path Coding School

React useCallback Hook: Super Easy Guide in 5 Min!

React useCallback Hook

Table of Contents

Hooks have revolutionized how developers write React JS applications! With React useCallback, you can optimize your components by memoizing functions. Simply remember the result of a heavily processed function and use it until the dependencies are updated.

Feel free to use code examples!

Learn how to prevent unnecessary re-renders and Boost your app performance!

Understanding the Basics of React Hooks 

React hooks are awesome! They are easy and simple — I love to use them.

Hooks let functional React components use state, and other React features.

For example, I can extract state and logic into a custom hook and reuse it across different components — a great way to share components and reduce code duplication!

Before hooks, when React only started gaining popularity, functional components had less control than class components.

React useCallback Hook

However, as time passed, developers wanted more powerful features to work with in a functional style. Finally, anticipated hooks were released in React 16.8 in Feb 2019.

React hooks initially caused some confusion because they represent a new programming paradigm in the React library.

As developers moved from class components (with lifetime methods) to functional components (with hooks), they faced a learning curve in understanding the new flow.

I was not an exception, even though I was already a “functional style” fan, lol!

React useCallback Hook

However, once the other developers and I got the concepts behind, hooks simplified the code and made it more readable.

Hooks encouraged me to write a more functional programming style, using pure functions and avoiding stateful logic.

The two most essential hooks are useState and useEffect — the first I learned. These two replace the need for class components and lifecycle methods! After using these hooks, I realized how much simpler my code became.

Some other commonly used hooks are useCallback, useMemo, useContext, and useReducer.

React useCallback Hook

All hooks provide a way to use React features without writing class components – hurray!

What is the React useCallback Hook?

useCallback returns a memoized version of a function. Think of the memoized version as a copy of a function code that Reacts saves and returns for you on each rendering.

With a memorized function, we can prevent React from re-creating the function on every render.

In a regular case, Javascript will create a new instance of the handleClick function every time a user clicks the Increment button.

function Parent() {
  const [count, setCount] = useState<number>(0)

  const handleClick = () => {
    console.log('Click!')
    setCount(count + 1)
  }

  return (
    <>
      <p>{count}</p>
      <button onClick={handleClick}>Increment</button>
    </>
  )
}

Whenever you click the Increment button, the handler function changes state, forcing React to re-render the count number. On re-render, Javascript initializes a new instance of the handleClick function and assigns it to the MyChild click.

Everything seems legit here. But what if you don’t want JavaScript to reinitialize the handler function and use the one already used before rendering?

To ensure that JavaScript will not reinitialize the handleClick function on every render, you can use the useCallback.

Memoizing the child React component is a good use case to keep the same function instance.

What Is Memoization?

Imagine you have a BigChild component that fetches data (or asynchronously retrieves it from any store) and renders it as a list of items. The Parent of this component passes a handler function that the BigChild component is assigned on every list item click. This is a very common situation, I can tell.

But, during the implementation, it turned out that the amount of data you need to render is colossal, and data retrieval takes 3 seconds every time…

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

type ItemType = {
  id: number;
};

function BigChild({ handler }: { handler: (index: number) => void }) {
  const [list, setList] = useState<ItemType[]>([]);

  useEffect(() => {
    const controller = new AbortController();

    // Immitates 3 seconds API call or any other async data retriving
    // setTimeout updates list state in 3000 ms
    const interval = setTimeout(() => {
      console.log('Data fetched!');
      setList(items);
    }, 3000);

    return () => {
      controller.abort();
      clearTimeout(interval);
    };
  }, []);

  return (
    <>
      {list.map(item => (
        <div onClick={() => handler(i)} key={item.id}>{item.id}</div>
      ))}
    </>
  );
}

3 seconds are too long — your clients can’t wait for that!

You must do anything to prevent this component from rendering. This is where memoization comes into play.

Memoization is a technique that stores the result of function calls and returns the cached result when the call occurs again.

Simply import a memo function and wrap the component to start using memorization.

import { memo } from 'react';

function BigChild({ handleClick }) {
 // ... BigChild implementation
}

export default memo(BigChild);

However, In the case of BigChild, memoization will fail because the code will differ due to a new handleClick function.

Memoization works only when there is an existing result for existing code in the memory.

If any part of the memoized function differed, such as its implementation or dependencies, the memoization will fail, and React will recalculate it.

Memoization relies on the function’s identity to determine whether to return a cached result or recalculate a new function. If the function’s identity changes, the cached result is no longer valid, and React must recalculate the function.

React useCallback Hook

To achieve the desired memoization for the BigChild component, we need to use both memo and useCallback.

memo function will memoize the biggest part of the component while useCallback memoizing the handleClick function.

Keep reading to see the result, as we need to discuss some other aspects before the final implementation

How Does The useCallback Hook Can Help?

useCallback prevents unnecessary re-renders of components.

You typically want to minimize the re-rendering time to deliver the best UX. Especially when dealing with complex logic or frequent updates!

Wrap a function in useCallback — React will memoize the function instance and return it as long as the dependencies array remains the same. 

If you deal with scenarios where callback functions are computationally expensive, useCallback significantly impacts performance.

Benefits of Using useCallback Hook

useCallback helps optimize your React app performance, stability, and readability. These are some main detailed advantages:

  1. Performance Optimization: improves performance in components with complex logic or frequent updates. Useful when passing callbacks as props to child components.
  2. Preventing Component Re-renders: No unnecessary re-renders unless the dependencies change!
  3. Stable References: Use any function safely in useEffect! useCallback ensures stable references, preventing stale closure issues.
  4. Improved Code Readability: The Dependencies array clarifies which values the function relies on.
  5. Optimizing Hooks: Memoize hook functions so React can memorize consuming components.
  6. Managing Large Applications: Improved performance in large applications with complex codebases.

I always use useCallback to create more efficient applications.

This is especially beneficial in areas where performance optimization is crucial for a smooth user experience.

React useCallback Hook

What is the useMemo Hook in React?

The useMemo in React memoizes the result of a function. It takes a function and an array of dependencies and returns a memoized value.

React will recalculate the memoized value only when dependencies or the code inside the function has changed.

In this example, React will memoize the result of the function passed to useMemo based on the a and b values. If a or b changes, React re-executes the function, and memoizes the new result.

The difference between useCallback and useMemo

The useCallback and useMemo hooks in React are similar — both memoize values to enhance UX.

However, they are used for different purposes and have some key differences:

useCallback useMemo
Purpose Memoizes function's code. Memoizes the result of a function.
Usage Optimizes functions passed as dependencies. Optimizes expensive or complex calculations.
Return Value A memoized version of the function. A memoized value.
Dependencies Array of dependencies; the memoized value is only recalculated if one of the dependencies changes.

useCallback memoizes functions, while useMemo memoizes values.

Both hooks boost performance and prevent unnecessary recalculations or recreations of functions/values.

Practical Implementation and Examples

The goal is to prevent unnecessary rerenders for a component with heavily loading data. This is a very common situation — you want to know how to handle it effectively. Find the code source here!

We already have some code written in this article. At this moment, we have two components, Parent and BigChild.

This is the Parent component – it initializes a clickHandler function and passes it to the BigChild component. The Increment button triggers the count state update and renders the component.

import BigChild from './BigChild'

function Parent() {
  const [count, setCount] = useState<number>(0)

  const handleClick = (index: number) => {
    console.log(`Click on BigChild Item #-${index}`)
  }

  return (
    <>
      <BigChild handler={handleClick} />
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}

The BigChild renders a list of data it gets in 3 seconds after initial rerender. Also, BigChild destructs the handler callback and assigns it to items’ clicks.

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

// Fake data
const items = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];

type ItemType = {
  id: number;
};

function BigChild({ handler }: { handler: (index: number) => void }) {
  console.log('BigChild Rerenders');

  const [list, setList] = useState<ItemType[]>([]);

  useEffect(() => {
    // Immitates API call or any other async data retriving
    // setTimeout updates list state in 1000 ms
    const timer = setTimeout(() => {
      setList(items);
    }, 1000);

    return () => clearTimeout(timer);
  }, []);

  return (
    <div>
      {list.map((item, i) => (
        <div onClick={() => handler(i)} key={item.id}>{item.id}</div>
      ))}
    </div>
  );
}

export default memo(BigChild);

The BigChild component is memoized using the React.memo, but it still rerenders on every Parent component rerender. You will see the BigChild Rerenders” string in the console from line 11 every time you click the Increment Button on the Parent component. It causes unnecessary rerenders.

This happens because the Parent passes a new handler function as a prop to BigChild on every rerender. 

React ignores the memoized version of BigChild and rerenders it unnecessarily since the reference to the handler function changes on every render

To fix this issue, use the useCallback hook in the Parent component and memoize the handleClick function.

How To Implement useCallback In React Application?

Import the useCallback from the React library and wrap a handleClick function with it.

Don’t forget to pass an empty array [] as the second argument to useCallback! This tells React always to remember the function and use the same instance for every rerender.

import { useState, useCallback } from 'react';
import BigChild from './BigChild';

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

  const handleClick = useCallback((index: number) => {
    console.log(`Click on BigChild Item #-${index}`);
  }, []);

  return (
    <>
      <BigChild handler={handleClick} />
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}

Now, React.memo will finally memoize the BigChild component as its props aren’t changed on the Parent rerender — perfect!

What Are Some Real-world Scenarios Where useCallback Can Be Beneficial?

In real-world projects, I’ve seen useCallback usage mostly for the following cases:

Optimizing Child Components:

  • When a parent component renders a child component, useCallback prevents unnecessary re-renders of the child component. When the Parent changes — the individual components don’t need to be recreated.

Optimizing Dependencies in useEffect:

  • In the useEffect hook, use useCallback to memoize functions used as dependencies. This ensures the effect only runs when necessary!

Optimizing Virtualized Lists:

  • Use useCallback when rendering large lists with libraries like react-window or react-virtualized. Prevent unnecessary re-renders of list items that are not currently in view.

Overall, useCallback is useful where you want to optimize the performance of components and improve the UX.

React useCallback Hook

Conclusion

The useCallback hook in React is a powerful tool! It boosts performance and improves the user experience in various scenarios.

In my experience, I’ve found useCallback useful when working with complex UI that involves heavy data manipulation.

This hook can prevent unnecessary re-renders and helps understand the nuances of React’s rendering cycle.

I highly recommend mastering the use of the Callback hook for anyone serious about React development! 

Thank you for reading this article — please, let me know if you have any suggestions! And keep learning!!

Welcome to the programming blog. Here, you’ll find insights, tutorials, and helpful resources.

Share the Post:
Join Our Newsletter