Skip to content

Commit

Permalink
docs(blog): update usecallback post (#6371)
Browse files Browse the repository at this point in the history
  • Loading branch information
necatiozmen authored Sep 26, 2024
1 parent a97c018 commit 1fa7b33
Showing 1 changed file with 190 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ description: Improve app performance with React useCallback() hook.
slug: react-usecallback-guide
authors: abdullah_numan
tags: [react]
image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2022-09-20-react-use-callback/social.png
image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2022-09-20-react-use-callback/social-2.png
hide_table_of_contents: false
---

**_This article was last updated on January 25, 2024 to expand code examples add more real use-cases for React useCallback hook._**
**_This article was last updated on January 25, 2024 to include comparisons between useCallback and useMeme and how to use useCallback with other hooks._**

## Introduction

Expand All @@ -25,14 +25,13 @@ In this post, we explore how to use React `useCallback` in order to memoize a fu

Steps we'll cover:

- [Introduction](#introduction)
- [What is React `useCallback` ?](#what-is-react-usecallback-)
- [Why Use React `useCallback` Hook?](#why-use-react-usecallback-hook)
- [Memoize Functions with React `useCallback`: Ensuring A Callback's Referential Equality](#memoize-functions-with-react-usecallback-ensuring-a-callbacks-referential-equality)
- [Referential Inequality of Callbacks: Observing Unnecessary Re-renders](#referential-inequality-of-callbacks-observing-unnecessary-re-renders)
- [Memoizing an Event Listener with `useCallback()`](#memoizing-an-event-listener-with-usecallback)
- [React `useCallback` with Dependencies](#react-usecallback-with-dependencies)
- [useCallback vs useMemo](#usecallback-vs-usememo)
- [When to Use React `useCallback`](#when-to-use-react-usecallback)
- [When Not to Use React's `useCallback` Hook](#when-not-to-use-reacts-usecallback-hook)
- [Bonus:useCallback with Other Hooks](#bonususecallback-with-other-hooks)
- [Example](#example)

## What is React `useCallback` ?

Expand Down Expand Up @@ -92,7 +91,7 @@ const UserPostsIndex = ({ signedIn }) => {
}, []);

return (
<div className="my-1 p-2 box">
<div className="box my-1 p-2">
<div className="m-1 py-1">
<h2 className="heading-md">Your Posts</h2>
<p className="m-1 p-1">{signedIn ? `Signed in` : `Signed out `}</p>
Expand Down Expand Up @@ -125,7 +124,7 @@ const UserPostsList = ({ userPosts, deletePost }) => {
return (
<div className="px-1">
{userPosts.map((post) => (
<div key={post.id} className="my-1 box flex-row">
<div key={post.id} className="box my-1 flex-row">
<UserPost post={post} />
<button
className="btn btn-danger"
Expand Down Expand Up @@ -154,9 +153,9 @@ const UserPost = ({ post }) => {
// console.log('Rendering UserPost component')

return (
<div className="my-1 flex-row-left">
<div className="flex-row-left my-1">
<a href={`#${post.title}`} className="">
<h4 id={post.title} className="px-2 font-sm font-bold">
<h4 id={post.title} className="font-sm px-2 font-bold">
{post.title}
</h4>
</a>
Expand Down Expand Up @@ -238,6 +237,90 @@ This is because `useCallback()` caches and produces the same copy of `deletePost

Notice we are passing a dependency in the array passed as the second argument of `useCallback`. With `userPosts` as a dependency, we want a change in `userPosts` to trigger a renewal of the function memo. So, every time the value of `userPosts` changes, a new `deletePost` function object will be created in `<UserPostsIndex />` and `<UserPostsList />` will be re-rendered.

## useCallback vs useMemo

Many devs, including myself from time to time, get confused as to when to use which.

Understanding the difference between `useCallback` and `useMemo` goes a long way in optimizing React applications. What follows is a simple comparison that should make their purposes clear and when each should be used.

#### **useCallback**

`useCallback` memoizes a function definition so that it doesn't get recreated on every render unless its dependencies change.

Comes in handy when you want to pass some callback functions to a child component wrapped with `React.memo` to avoid unnecessary re-renders.

```jsx
let handleClick = useCallback(() => {
// Function logic here
}, [dependencies]);
```

In the example, `handleClick` will have the same reference on every render unless dependencies change.

#### **useMemo**

`useMemo` memoizes the result of a function call, caching the value that would be returned.
Perfect for expensive calculations that you don't want to recompute on every render unless certain dependencies change.

```jsx
const computedValue = useMemo(() => {
return expensiveCalculation(data);
}, [data]);
```

Here, `computedValue` would only be re-evaluated when `data` changes.

#### **Key Differences**

- **What They Return**:
- `useCallback` returns a memoized **function**.
- `useMemo` returns a memoized **value**.
- **Main Usefulness**:
- `useCallback` prevents unnecessary re-creations of functions.
- `useMemo` avoids recalculating a value when it is not necessary.

#### **When to Use useCallback**

- **Preventing Re-renders of Children**: When implementing the `useCallback` hook with a function passed as a prop to a child that is optimized by `React.memo`, it ensures the reference of that function does not get recreated unnecessarily.
- **Event Handlers**: If functions are created on every render—event handlers, for example—you can keep its reference by using `useCallback`.

#### **When to Use useMemo**

- **Expensive Computations**: When you have a function that performs a heavy computation, and you want to avoid it running on each render.
- **Derived Data**: Computed result based on props or state which should not re-run unless the dependencies change.

#### **Analogy**

- Think of `useCallback` as memoizing the **function itself**, whereas `useMemo` memoizes the **result of the function**.

#### **Practical Example**

For instance, consider the following filtering list scenario:

- **Using useMemo**:

```jsx
const filteredItems = useMemo(() => {
return items.filter((item) => item.active);
}, [items]);

return <ItemList items={filteredItems} />;
```

Here, `filteredItems` is re-evaluated only when `items` change.

- **With useCallback**:

```jsx
const getFilteredItems = useCallback(() => {
return items.filter((item) => item.active);
}, [items]);
return <ItemList getItems={getFilteredItems} />;
```

In this case, `getFilteredItems` doesn't change its function reference if `items` haven't changed.

## When to Use React `useCallback`

React `useCallback` hook is intended to memoize a callback function by maintaining its referential integrity. It should not be confused with `useMemo` which is used to cache the value of a function.`useCallback` is commonly used for memoizing functions between re-renders.
Expand All @@ -250,6 +333,102 @@ React `useCallback()` is also used to cache callbacks in debouncing, as well as

We should not use the `useCallback` hook for memoizing the value of a function. We should also avoid overusing `useCallback` since the hook itself contributes to the component's cost. It is recommended to use the `useCallback` hook only in cases referential equality of the function is important.

## Bonus:useCallback with Other Hooks

I thought it would be worth adding a section to our article about how `useCallback` can be used with other React Hooks.

Knowing how `useCallback` works with other React Hooks will make it easier to write good-performing, efficient components. Following are ways that we can combine `useCallback` with other Hooks.

#### **useCallback and useEffect**

When we use `useEffect`, we need to include functions in the dependency array as well. If this function changes at each render, that also will make the `useEffect` run unnecessarily. This is where we wrap up the function with `useCallback`.

```jsx
import React, { useState, useEffect, useCallback } from "react";

function SearchComponent() {
const [query, setQuery] = useState("");

const fetchResults = useCallback(() => {
// Fetch data based on query
}, [query]);

useEffect(() => {
fetchResults();
}, [fetchResults]);

return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
```

Above, in the example, `fetchResults` is memoized with `useCallback`, which means that it only changes when `query` changes, so `useEffect` does not run on every render.

#### **useCallback and useMemo**

While `useCallback` memoizes functions, `useMemo` memoizes values. Sometimes, we may want to use both to optimize our components.

```jsx
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

const handleClick = useCallback(() => {
doSomethingWith(memoizedValue);
}, [memoizedValue]);
```

Here, we memoize an expensive computation using `useMemo`, then take that value and memoize a function that uses it with `useCallback`.

#### **useCallback and useContext**

With context, passing down functions may cause re-renders when the function reference changes. Using `useCallback` allows us to prevent unnecessary updates in context consumers.

```jsx
const MyContext = React.createContext();

function MyProvider({ children }) {
const [state, setState] = useState(0);

const increment = useCallback(() => {
setState((prev) => prev + 1);
}, []);

return (
<MyContext.Provider value={{ state, increment }}>
{children}
</MyContext.Provider>
);
}

function ChildComponent() {
const { state, increment } = useContext(MyContext);
// ChildComponent won't re-render unless state or increment changes
}
```

By memoizing `increment`, we make sure that the context value isn't changed when it doesn't need to be, thereby optimizing the components consuming the context.

#### **useCallback and Custom Hooks**

When writing custom Hooks, we can always use `useCallback` to memoize functions returned by the hook so that consuming components get stable function references.

```jsx
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);

const handleResize = useCallback(() => {
setWidth(window.innerWidth);
}, []);

useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [handleResize]);

return width;
}
```

In this custom hook, we memoize `handleResize` with `useCallback`, so that the event listener isn't set anew after every render.

## Summary

In this post, we demonstrated how to leverage the React `useCallback` hook in preventing unnecessary re-renderings originating from a callback function's referential inequality.
Expand Down

0 comments on commit 1fa7b33

Please sign in to comment.