useCallback Hook

useCallback Hook

·

7 min read

Introduction

The useCallback hook is a powerful tool in the React developer's toolkit, allowing for the optimization of component rendering and improving the overall performance of your application. In this article, we will explore what the useCallback hook is, how it works, and some examples of how it can be used in your React components.

First, let's start by discussing what the useCallback hook is and how it differs from the useMemo hook. The useMemo hook is used to memoize the result of a calculation, meaning that it will only re-calculate the result if one of the inputs to the calculation changes. The useCallback hook, on the other hand, returns a memoized (cached) version of a function, ensuring that it is only recreated if one of its dependencies changes.This allows us to isolate resource intensive functions so that they will not automatically run on every render.

Note: Memoization means caching a value so that it does not need to be recalculated.

Using The Hook

To use the useCallback hook in a React component, you first need to import the hook from the React library. This is done by adding the following line of code at the top of your component file:

import { useCallback } from 'react';

Next, we need to declare a callback function that we want to memoize using the useCallback hook. This is done by defining a new function inside of the useCallback hook, like so:

const cachedCallback = useCallback(() => {
    // Function body here
}, [dependency1, dependency2, ...]);

In this example, we have defined a new function named cachedCallback that will be memoized/cached by the useCallback hook. The function body contains the code that will be executed when the callback is called.

The useCallback hook takes two arguments: the callback function and an array of dependencies. The dependencies are variables or values that the callback function relies on. If any of these dependencies change, the callback function will be recreated. If none of the dependencies change, the callback function will be reused, improving the performance of your application.

Once we have defined the memoized callback function using the useCallback hook, we can use it like any other function in our component. For example, if we wanted to pass the cachedCallback function as a prop to a child component, we would do so like this:

<ChildComponent onClick={cachedCallback} />

In this example, the cachedCallback function will only be recreated if one of the dependencies specified in the useCallback hook has changed. This can prevent unnecessary re-renders and improve the performance of your application.

An example of a React component that uses the useCallback hook to optimize the rendering of a child component:

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

export const ParentComponent = () => {
    const [value, setValue] = useState(0);

    // Define a memoized callback function using the useCallback hook
    const incrementValue = useCallback(() => {
        setValue(prevValue => prevValue + 1);
    }, [value]); // The value state variable is a dependency

    return (
        <div>
            <ChildComponent onClick={incrementValue} />
        </div>
    );
}

In this example, the incrementValue function is defined using the useCallback hook. The value state variable is specified as a dependency, meaning that the incrementValue function will be recreated if the value state variable changes. The incrementValue function is then passed as a prop to the ChildComponent, which will call the function whenever it is clicked.

By using the useCallback hook, we can ensure that the incrementValue function is only recreated if the value state variable has changed, preventing unnecessary re-renders and improving the performance of our application.

Use Cases

  • One common use case for the useCallback hook is when passing a callback function as a prop to a child component, as in the example above. Without the useCallback hook, the child component will re-render every time the parent component re-renders, even if the callback function itself has not changed. This can lead to unnecessary rendering and poor performance. By using the useCallback hook, we can ensure that the callback function is only recreated if one of its dependencies has changed, preventing unnecessary re-renders and improving performance.

  • Another important use case for the hook is when passing a callback function as a dependency to a useEffect hook. Since a function is stored as a reference object (more about this here), each time the useEffect renders, the memory address of the function changes causing the useEffect to treat it as a new value, thereby causing a re-render of the hook. This results in an infinite loop of the useEffect, which can be very memory-intensive.

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

export const App = () => {
    const [url, setUrl] = useState('https://api.adviceslip.com/advice');
    const [advice, setAdvice] = useState('')

    // function to fetch data from an API
    const fetchData = async () => {
          const response = await fetch(url)
          const json = await response.json()
          const data = json.slip.advice

          return data
    }

// run the function each time the component refreshes/the url changes
useEffect(() => {
      setAdvice(fetchData())
      console.log(fetchData())
}, [ url, fetchData ])

    return (
        // render the data (advice) on the page
        <div className='App' >
            {advice}
        </div>
    );
}

The example above mimics creating a reusable function which can be used to fetch data from an API (in this case, the Advice Slip API for generating random advice). The useEffect is then used to set the value of advice to the data returned by the function, and also log it to the console only when the component loads or when any of the dependencies change, in this case the url and the function. The function is added to the useEffect dependency array because it is used inside the useEffect (as part of best practices).

However, this causes an infinite loop in which the useEffect continuously recreates the function because the memory address (where the function is stored in memory) changes each time the function runs. Apart from the huge cost this can incur in your application, this can make your application freeze and also cause a lot of other bugs.

To fix this, simply wrap the fetchData function with the useCallback hook:


    // function to fetch data from an API
    const fetchData = useCallback(async () => {
          const response = await fetch(url)
          const json = await response.json()
          const data = json.slip.advice

          return data
    }, [url])

After wrapping the function in useCallback, the url is passed as a dependency to the useCallback so that it re-runs the function whenever the url changes. By doing it like this, the function is cached, thereby preventing a re-run unless the url changes.

Since the url is already in dependency array for the useCallback, there is no need adding it to the useEffect dependency array. Therefore the useEffect can be rewritten thus:

useEffect(() => {
      setAdvice(fetchData())
      console.log(fetchData())
}, [fetchData])

This can greatly save costs and also improve the performance of your application.

  • Another example of how the useCallback hook can be used is when working with event handlers. Without the useCallback hook, event handlers in React components will be recreated every time the component re-renders. This can lead to unexpected behavior and bugs in your application. By using the useCallback hook, we can ensure that event handlers are only recreated if one of their dependencies has changed, allowing for more predictable behavior and fewer bugs.

Best Practices

When using any external value inside the useCallback hook, it is advised to also add it to the dependency array of the hook even if it's not a dependency. This makes your codebase clearer and easier to understand.

Conclusion

In conclusion, the useCallback hook is a valuable tool for optimizing the performance of your React application. By memoizing callback functions and event handlers, the useCallback hook can prevent unnecessary re-renders and improve the overall performance of your application. Give it a try in your next React project and see the benefits for yourself.

Read More

Everything you need to know about all React hooks here, including those you didn't know existed

Did you find this article valuable?

Support Paul Saje by becoming a sponsor. Any amount is appreciated!