Exploring React Hooks: A Comprehensive Tutorial Series — Part 4: useCallback

Elevate Your React Development with Hooks — Simplifying State and Lifecycle Management

Vinojan Veerapathirathasan
5 min readJul 1, 2024
React Hooks: useCallback

Hi there 👋,

Welcome to the fourth part of the React Hooks series! In this part we will explore about useCallback hook with the practical explanation. Let’s deep dive into the topic 🎊

useCallback

The useCallback hook in React is crucial for optimizing the performance of components that rely heavily on functions, especially when these functions are passed as props to child components. Similar to useMemo, which memoizes the result of a function, useCallback returns a memorized version of the callback function itself. This prevents unnecessary re-renders of child components that depend on these functions if the function dependencies have not changed.

Figure 1: useMemo vs useCallback

Development Needs for useCallback

In complex React applications, especially those with deep component trees, passing callbacks deep down the component tree can lead to performance issues if these components re-render too often. This is typically because functions are re-created on every render, making shallow comparison of props fail in child components, leading to unnecessary renders. useCallback helps mitigate this by ensuring that function identities are stable across renders if their dependencies remain unchanged.

Example 1:

let’s consider a simple button click scenario where a callback function is needed to update a count:

import { useState, useCallback } from 'react';

function Counter() {
const [count, setCount] = useState(0);

const increment = useCallback(() => {
setCount(c => c + 1);
}, []); // Dependencies array is empty, meaning the function never changes

return (
<button onClick={increment}>Increment</button>
);
}

export default Counter;

In this example, the increment function is wrapped in useCallback with an empty dependency array, indicating that it does not depend on any values from the component scope and should remain the same for the lifetime of the component. This is particularly useful if this function were to be passed to deeply nested child components.

Example 2:

Now, let’s a simple application with a list of users, a shuffle button, and a search input to filter users by name. The key focus is on how useCallback can be used to prevent unnecessary re-renders of components that receive functions as props:

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

const allUsers = ['Vinojan', 'Abhimanyu', 'Aji', 'Vino', 'Jaksha'];

const shuffle = (array: string[]) => {
let currentIndex = array.length, randomIndex;
// Create a copy of the array to shuffle to avoid mutating the original array
let arrayCopy = [...array];

while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[arrayCopy[currentIndex], arrayCopy[randomIndex]] = [
arrayCopy[randomIndex], arrayCopy[currentIndex]];
}

return arrayCopy;
};

const Users = () => {
const [users, setUsers] = useState(allUsers);

const handleSearch = (text: string) => {
const filteredUsers = allUsers.filter((user) =>
user.toLowerCase().includes(text.toLowerCase()),
);
setUsers(filteredUsers);
}

return (
<div className='tutorial'>
<div className='align-center mb-2 flex'>
<button onClick={() => setUsers(shuffle(allUsers))}>
Shuffle
</button>
<Search onChange={handleSearch} />
</div>
<ul>
{users.map((user) => (
<li key={user}>{user}</li>
))}
</ul>
</div>
);
}
export default Users
// /_components/search.tsx

import { memo } from 'react';

interface SearchProps {
onChange: (text: string) => void;
}

const Search = ({ onChange }: SearchProps) => {
console.log('Search rendered'); // To monitor when the Search component re-renders
return (
<input
type="text"
placeholder="Search users..."
onChange={(e) => onChange(e.target.value)}
/>
)
}

export default memo(Search);

`memo`

The memo function is a higher-order component provided by React for optimizing performance, especially in components that receive props. It is primarily used to prevent unnecessary re-renders of functional components by memorizing the result of a render based on specific inputs (usually props).

When a component is wrapped with memo, React performs a shallow comparison of the previous and new props. If the comparison results show that the props have not changed, React skips rendering the component and reuses the last rendered result.
Here, If the onChange function is not changed, the Search component will not be re-rendered.

Use Cases: It is most beneficial in components that:
- Receive props that frequently have the same values.
- Are relatively expensive to render.
- Render frequently due to parent component re-renders.

So here, We have to be wrapped the handleSearch with useCallback to ensure it retains the same reference across renders unless its dependencies change. This is crucial because the Search component is wrapped with memo, which only re-renders if its props change.

// other code

const handleSearch = useCallback((text: string) => {
console.log('First user', users[0]); // Output: always 'Vinojan'

const filteredUsers = allUsers.filter((user) =>
user.toLowerCase().includes(text.toLowerCase()),
);
setUsers(filteredUsers);
}, [users]);
// This function will be freezed on render this component, After that its memorizing the function.
// To freeze the function, remove the users into the dependancy array, it means handleSearch won't change

// return function

NOTE: It’s essential to correctly manage the dependencies of useCallback. If handleSearch used any state or props that change over time, those should be included in the dependency array to ensure that the function updates when needed.

How the Frontend Development Became Easier After the useCallback

useCallback has made frontend development more efficient by:

  • 🔄 Reducing Render Cycles: Ensuring function identities are stable reduces unnecessary renders in components that receive these functions as props.
  • 🛠️ Optimizing Performance in Deep Component Trees: In applications with many nested components, useCallback prevents a ripple effect of re-renders through the component tree.

As we have seen, we covered the practical use and benefits of useCallback in optimizing React applications. This understanding is crucial for writing efficient React code, especially in larger applications with complex component trees.

I appreciate you taking the time to read this article.🙌

🔔 Stay tuned for the next article in this series, where we will explore useContext, another powerful hook for managing global state in a React application. This will help simplify state management across different levels of the component tree, reducing prop drilling.

Before you move on to explore next article, don’t forget to give your claps 👏 for this article and leave your feedback💭 on the comment section.
Stay connected with me on social media. Thanks for your support and have a great rest of your day! 🎊

Part 1: useState — https://medium.com/@imvinojanv/exploring-react-hooks-a-comprehensive-tutorial-series-part-1-usestate-3c1ce8378e95

Part 2: useEffect — https://medium.com/@imvinojanv/exploring-react-hooks-a-comprehensive-tutorial-series-part-2-useeffect-dd2d63778af8

Part 3: useMemo — https://medium.com/@imvinojanv/exploring-react-hooks-a-comprehensive-tutorial-series-part-3-usememo-6c694f485d95

✍️ Vinojan Veerapathirathasan.
LinkedIn : https://www.linkedin.com/in/imvinojanv/
Email: imvinojanv@gmail.com

--

--

Vinojan Veerapathirathasan

Software Engineer at EL | Designer | Medium Writer | AI Enthusiast | Entrepreneur | Specializing in Modern Web Application Development