November 22, 2022

React useCallback Hook With Examples

As a performance optimization, React lets you cache (memoize) a component, a function or a computed value.

If you want to memoize a component then you can use React.memo.

If you want to memoize a computed value then you can use useMemo() hook.

If you want to cache a function during component re-renders then you can use useCallback() hook in React.

Why useCallback hook

In JavaScript, functions are First-class functions (or first class objects) which means functions in JS are treated like any other variable. For example, a function can be passed as an argument to other functions, can be returned by another function and can be assigned as a value to a variable.

Read more about it here.

Since functions are considered objects so rules of reference equality will apply on functions in JS. Consider the following example where sayHello() function returns another function.

function sayHello() {
  return () => {
    console.log("Hello!");
  };
}
const f1 = sayHello();
const f2 = sayHello();
console.log(f1 === f2); // false
console.log(f1 === f1); // true

f1 and f2 holds two instances of the same function but they are considered separate instances. Comparing them returns false because they have separate references.

What does that mean in terms of React components? Well, when the components are re-rendered the same function in the component is re-created (a separate instance from the previous copy of the same function). For example, a quite common scenario where you call a function as an event handler to add another object to array of objects.

  const [persons, setPerson] = useState([]);
  const addToPerson = (personData) => {
    setPerson(()=>{
      return [...persons, personData];
    });
  };

This function will be considered a different object every time the component, where this function resides, re-renders.

How does useCallback hook work

In React, when a component re-renders, React re-renders all of its children recursively. When you want to optimize rendering performance, you will sometimes need to cache the functions that you pass to child components.

In order to cache a function between re-renders of your component, wrap the function definition into the useCallback Hook.

Syntax of React useCallback hook is as give below-

useCallback(func, dependencies)

Parameters

func: The function that you want to memoize. It can take any arguments and return any values. React will return your function back to you during the initial render and cache it. On subsequent renders, React will return the cached function if the dependencies have not changed since the last render. If the dependencies have changes, it will give you the function that you have passed during the current render, and store it in case it can be reused later.

dependencies: An array of all values referenced inside of the function.

useCallback React example

You don't have to wrap all the function in a component inside useCallback hooks. One use case, where it may be a good step to Cache a function with useCallback, is when you pass a function as a prop to a component wrapped in memo.

In the example there is a component PersonList.js which displays the list of person objects and also provides the functionality to add a new person object.

In the Parent component (App.js) there is a state for theme to toggle between dark and light mode. State for array of person objects is also managed here.

PersonList.js

import React, { useState } from "react";

const PersonList = (props) => {
    console.log('In PersonList Component')
    const [name, setName] = useState('');
    const changeHandler = (event) =>{
        setName(event.target.value);
    }
    const clickHandler = () => {
        const personData = {
            id: Math.round(Math.random() * 10),
            name: name
        }
        props.addPerson(personData);
        setName('');
    }
    return(
        <div>
            <h2>Person List</h2>
            {props.persons.map((person) => {
                return <p key={person.id}>{person.name}</p>
            })}
            Enter Name: <input type="text" name="pname" value={name} onChange={changeHandler}></input>
            <button onClick={clickHandler}>Add Person</button>
        </div>
    );
}

export default React.memo(PersonList);

Note the wrapping of PersonList in memo to avoid its re-rendering for theme change.

App.js

import { useState } from 'react';
import PersonList from './components/MemoDemo/PersonList';
import './components/MemoDemo/theme.css';

function App() {
  const [isDarkMode, setIsDarkMode] = useState(0);
  const [persons, setPerson] = useState([]);
  const addToPerson = (personData) => {
    setPerson(()=>{
      return [...persons, personData];
    });
  };

  return (

    <>
      <input
        type="checkbox"
        checked={isDarkMode}
        onChange={e => setIsDarkMode(e.target.checked)}
      />
      Dark mode

      <hr />
      <div className={isDarkMode ? 'dark-mode' : 'light-mode'}>
        <PersonList persons={persons} addPerson={addToPerson} />
      </div>
    </>  
  );
}

export default App;

Theme.css

.dark-mode {
  background-color: black;
  color: white;
}
.light-mode {
  background-color: white;
  color: black;
}

Since you have wrapped PersonList inside React.memo so you may think that it should not be re-rendered for the theme state change as theme is not one of the prop for PersonList.

<PersonList persons={persons} addPerson={addToPerson} />

But that doesn't happen and PersonList is re-rendered for theme changes too.

React useCallback Hook

That is happening because of the re-creation of the function addToPerson(). As already explained above; function, if recreated again, is considered a different instance. Since it is one of the props addPerson={addToPerson} and function is considered a new instance so new props and old props are not same thus the PersonList component is re-rendered despite using React.memo.

That's where you can use useCallback() to cache the function itself. You just need to wrap the addToPerson function in App.js inside useCallback.

const addToPerson = useCallback((personData) => {
  setPerson(()=>{
    return [...persons, personData];
  });
}, [persons]);

Now change in theme state doesn't render the PersonList component.

useCallback example

That's all for the topic React useCallback Hook With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment