ReactJs useCallback hook

ReactJs useCallback hook

In this blog, we will see React js useCallback hook and how we can use it.

What is useCallback() hook?

React useCallback has a specific function: avoid unnecessary re-renders in your code, making your application faster and more efficient. It looks as follow:

const memoizedCallback = useCallback(() => {
     doSomething(a, b);
},[a, b]);

The hook takes 2 arguments: a callback function and dependency array. It returns a memoized version of that callback function that only changes if one of the dependencies has changed.

Basically function wrap inside useCallback() hook. It tells react to not re-create a wrapped function when component re-renders, unless any value of the useCallback's dependency array is changed.

First, let's see an example

Example app

Take a look at the sample app. App.js has an input field that takes a number and returns factors of that number. The toggle button changes the theme. Lists.js component display factors.

Screenshot (26).png

App.js

import { useState, useCallback } from "react";
import { List } from "./components/List";
import './App.css';

function App() {
    const [number, setNumber] = useState(0);
    const [isDark, setDark] = useState(false);

    const getFactors = () => {
        let factors = []
        for(let i = 1; i <= number/2; i++) {
            if(number % i === 0) {
                factors.push(i)
            }
        }
        return factors
    }
    const theme = {
        color: isDark ? "#ffffff" : "#333333",
        backgroundColor: isDark ? "#333333" : "#ffffff"
    }
    return (
        <div style={theme} className="App">
            <input type="number" placeholder="number" onChange={(e) => setNumber(e.target.value)}/>
            <button onClick={() => setDark(prevState => !prevState)}>Toggle Theme</button>
            <List getFactors={getFactors}/>
        </div>
    );
}

export default App;

As shown in code, all states and functions are in App.js. Child component List.js acts as a container to show data.

List.js

import { useState, useEffect } from "react";

export const List = ({getFactors}) => {
    const [items, setItems] = useState([]);
    // will update 
    useEffect(() => {
        console.log("Calculating... (from list)")
        setItems(getFactors());
    },[getFactors])

    return (
        <ul>
            {
                items?.map((item) => (
                    <li key={item}>{item}</li>
                ))
            }
        </ul>
    )
}

In the List.js component, we have useEffect which would be called whenever the getFactors() function is updated.

demo.gif

If we run our app, notice that When we give input or change input the console logs Calculating... (from list) as we expect. But the problem is that when we toggle the theme it also calls that getFactors() function, console logs Calculating... (from list).

The reason we are running into this problem is that In React, whenever a component re-renders, a new instance of the function in it gets generated. So the getFactors() function inside our App.js gets re-created every time when App.js is re-rendered. A new instance of the getFactors() is created even if we don't change the input value.

Solution

We can use the useCallback hook to prevent this. useCallback will not re-run code wrap inside of it unless the dependency array is changed. So the function will only re-run when it needs to. For example, when changing input value. So let's import the useCallback hook.

import { useCallback } from "react";

and wrap our getFactors() function in useCallback() and add dependency which is number.

const getFactors = useCallback(() => {
        let factors = []
        for(let i = 1; i <= number/2; i++) {
            if(number % i === 0) {
                factors.push(i)
            }
        }
        return factors
},[number])

Result

result_Trim.gif

Now notice, when we Toggle Theme it is doesn't console logs Calculating... (from list). getFactors() function will only be re-created when dependency array is changed, that is number is changed.

CodeSandBox

When to use useCallback?

In most cases, re-creating function instances is cheap, it won't affect the application. But, warping every function inside useCallback will cause performance issues, instead of solving them. useCallback will be called on every re-render and we also have to declare the dependency array.

So when to actually use it?

  • Referential Equality

Functions are objects in javascript. An example to understand object references.

const sum = (a, b) => {
     return (a + b)
}
const sum1 = (1 + 2);    // has a unique object reference.
const sum2 = (1 + 2)     // has a unique object reference.

sum1 === sum2            // false

Here, sum1 and sum2 share the same source code but they are different function objects. Compare sum1 === sum2 evaluates to false.

  • Re-computation is expensive

Imagine you have a function that returns a list, containing hundreds of items. Re-creating that function and rendering would be expensive. so we can use useCallback here.

Conclusion

  • useCallback hook is useful to avoid unnecessary re-renders in your code, making your application faster and more efficient.
  • Re-creating functions is cheap, it won't affect the application. But, warping every function inside useCallback will cause performance issues.