WittCode💻

React useCallback Hook

By

Learn how to use the useCallback React hook, including memoization with useCallback and react.memo, useCallback dependencies, how useCallback increases application performance, and what a function reference is.

Table of Contents 📖

Memoization

To understand the useCallback hook, we need to first understand memoization. Memoization is a technique that remembers the output of a function rather than recalculating it. In other words, memoization caches output values based on the input. If the input has been used before then the cached output value is returned as opposed to recalculating it. This increases the efficiency of an application as it minimizes the amount of times expensive functions/computations need to be ran or re-computed.

useCallback and Memoization

The useCallback hook returns a memoized function. In JavaScript, functions are stored as references. Therefore, memoizing a function caches the reference. Memoizing a function is useful in React because if a function reference that is being passed as a prop to another component changes, it will trigger a re-render. Therefore, memoizing a function with useCallback can prevent a component re-rendering unnecessarily.

Demonstration Setup

As a demonstration, lets create two Components called App and List. The List component will accept a function called handleClick as a prop and will list out 100

tags. When one of these

tags is clicked, the handleClick function will be called.

const List = ({handleClick}) => { console.log('Rendering List component');

return (
    <div>
        {[...Array(100)].map((val, index) => <h4 onClick={handleClick} key={index}>User {index}</h4>)}
    </div>
);

};

export default List;

Now lets create our App component that will display the List component, create the handleClick function that is passed to the List component, and handle some state.

const App = () => { console.log('Rendering App component');

const [clickedOn, setClickedOn] = useState('No user clicked...');

const handleClick = event => {
    const userText = event.target.innerText;
    setClickedOn(userText)
};

return (
    <main>
        <h1>Welcome to my App</h1>
        <h2>List of users</h2>
        <h3>You clicked on {clickedOn}</h3>
        <List handleClick={handleClick} />
    </main>
)

}

export default App;

Here, when an

tag from the List component is clicked, it will change the state of the App component as the handleClick method will be triggered, causing the App component's state to change. When the App component's state changes it is re-rendered, causing the handleClick method to be recreated. As the handleClick method is passed as a prop to the List component, the List component is re-rendered too. To demonstrate, click on one of the list items and look in the console for Rendering List component and Rendering App component. In this case, the re-rendering of the List component is not only unecessary but expensive. It is unecessary as we are only updating some text in the App component and it is expensive because the List component is listing out 100 elements.

React.memo

To stop the unnecessary re-rendering of our List component, we can use a combination of React.memo and useCallback. The React.memo function and useCallback hook are often used together. The React.memo function memoizes a component. Specifically, the React.memo function checks for prop changes. If it detects any, the component is re-rendered. If the props stay the same, then the component is not re-rendered. As useCallback returns a memoized function, if we pass a memoized function as a prop to a memoized component, then the component will not re-render. To memoize the List component, import the memo function from React and place the list component inside it.

import { memo } from "react";

const List = memo(({handleClick}) => { console.log('Rendering List component'); return (

{[...Array(100)].map((val, index) =>

User {index}

)}
); });

export default List;

useCallback

Now that we've memoized our List component, we need to memoize the handleClick function that is created inside our App component. If we memoize the handleClick method, then the function reference that is passed as a prop to the List component will not change. As a result, React.memo will not detect any prop changes and the List component won't be re-rendered. We can memoize a function with the useCallback hook.

import { useCallback } from "react";

const App = () => { console.log('Rendering App component');

const [clickedOn, setClickedOn] = useState('No user clicked...');

const handleClick = useCallback(event => {
    const userText = event.target.innerText;
    setClickedOn(userText)
}, []);

return (
    <main>
        <h1>Welcome to my App</h1>
        <h2>List of users</h2>
        <h3>You clicked on {clickedOn}</h3>
        <List handleClick={handleClick} />
    </main>
)

}

export default App;

The useCallback hook takes a function to memoize as the first argument, an array of dependencies as the second, and returns a memoized function reference. When any of the dependencies provided in the second argument change, the function reference changes. Here, we don't have any dependencies listed (we provided an empty array). Now, as the handleClick function and List component are memoized, if we click an

tag from the List component, only the App component is re-rendered. To demonstrate, click one of the

tags and look in the console. Only Rendering App component is listed, not Rendering List component.

useCallback and Dependencies

Now lets demonstrate some dependency usage with useCallback. First we'll add some extra state to our App component. This state will keep track of the users that are listed out in the List component. Lets also create a memoized addUser function that adds a user to the list when a button in the List component is clicked. However, lets make it so that a new function reference is generated when the users state changes. We can do this by listing it as a dependency.

const App = () => { console.log('Rendering App component...');

const [clickedOn, setClickedOn] = useState('No user clicked...');
const [users, setUsers] = useState([...Array(100)]);

const handleClick = useCallback(event => {
    const userText = event.target.innerText;
    setClickedOn(userText)
}, []);

const addUser = useCallback(() => {
    setUsers((u) => [...u, 1] );
}, [users]);

return (
    <main>
        <h1>Welcome to my App</h1>
        <h2>List of users</h2>
        <h3>You clicked on {clickedOn}</h3>
        <List handleClick={handleClick} users={users} addUser={addUser} />
    </main>
)

}

export default App;

In the List component lets now iterate over the users that are passed in and also add a button that calls the addUser function when it is clicked.

const List = memo(({handleClick, users, addUser}) => { console.log('Rendering list...');

return (
    <div>
        <button onClick={addUser}>Add User</button>
        {users.map((val, index) => <h4 onClick={handleClick} key={index}>User {index}</h4>)}
    </div>
);

});

export default List;

Here, when we click the button a user is added to the users state in the App component which triggers a re-render. Then, even though the addUser method is memoized, a new reference is generated as its dependency, the users state, changed. We can see this by looking at the list of

tags and seeing new users being added at the bottom.

When to use useCallback

The useCallback hook should be used to prevent unnecessary re-renders. However, there is a trade off between code readability and re-rendering. In other words, an unnecessary re-render isn't always detrimental to an application's performance and adding useCallback everywhere can make code more complicated and hard to read. Simply put, premature performance optimization can cause issues down the line. Therefore, the useCallback hook is best suited for computationally heavy calculations.

React useCallback Hook