Optimizing React Components: A Guide to useCallback and useMemo
Tags: react, hooks, nextjs, redis
Published: 2023-10-16 03:33:19


## Understanding the Difference Between `useCallback` and `useMemo` in React

In the world of React, performance optimization is a crucial aspect of building efficient applications. Two often-used tools for optimizing child components are the `useCallback` and `useMemo` hooks. These hooks allow you to cache data or functions, preventing unnecessary re-renders and improving the overall performance of your application. In this blog post, we will explore the differences between these two hooks, using real-life examples to illustrate when to use each.

## The Purpose of `useMemo` and `useCallback`

Before diving into the differences, let's clarify the purpose of `useMemo` and `useCallback`.

**`useMemo`:** This hook is used to cache the result of calling a function. It's particularly helpful when you want to avoid recalculating a value on every render. The result is only recalculated if one or more of the specified dependencies change.

**`useCallback`:** In contrast, `useCallback` caches the function itself, rather than its result. It's perfect for preventing a function from being recreated on each render, ensuring that it remains consistent as long as the specified dependencies do not change.

## Real-Life Example: ProductPage

Let's consider a real-life example involving a `Product` component. This component displays product information and a form for making a purchase. We'll use this example to illustrate when to use `useMemo` and when to use `useCallback`.

First we need to fetch data. I am using _**Upstash Redis**_ as database. Lets define the home page.
```jsx
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

export const revalidate = 0; // disable cache

export default async function ProductPage({ productId }) {
  const productDetails = await redis.get(`product:${productId}`);

  // Parse the JSON data if your product details are stored as JSON in Redis
  const product = JSON.parse(productDetails);

  return (
    <div className="container mx-auto p-4">
      <main className="main">
        <h1 className="text-3xl font-bold">Product Details</h1>
          <div>
            <p className="text-lg mt-2">Product Name: {product.name}</p>
            <p className="text-lg">Price: ${product.price.toFixed(2)}</p>
            {/* Add more product details here */}
          </div>
          <Shipping product={product} referrer={referrer} />
      </main>
    </div>
  );
}
```
Now lets define the shipping component.
```jsx
// Shipping.tsx
"use client";
import { useMemo, useCallback } from 'react';
import ShippingForm from "@/components/shippingform"

function ShippingComponent({ product, referrer }) {
  const requirements = useMemo(() => {
    return computeRequirements(product);
  }, [product]);

  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + product.id + '/buy', {
      referrer,
      orderDetails,
    });
  }, [product.id, referrer]);

  return (
    <div>
    {/* Another Client Component */}
      <ShippingForm requirements={requirements} onSubmit={handleSubmit} />
    </div>
  );
}
```

### `useMemo` in Action

In this example, `requirements` is memoized using `useMemo`. It calculates the requirements based on the `product`. This is useful because `computeRequirements` can be a computationally expensive operation. By caching the result of `computeRequirements(product)`, we ensure that the `requirements` object doesn't change unless the `product` changes. This prevents unnecessary re-renders of the `ShippingForm` component.

### `useCallback` in Action

On the other hand, the `handleSubmit` function is cached using `useCallback`. This function is tied to the `productId` and `referrer` values, and we want to ensure that it doesn't get recreated every time the component re-renders. This is particularly important because `handleSubmit` is an event handler used when the user submits the form. By caching the function itself with `useCallback`, we make sure that the code won't run until the user actually submits the form, improving performance.

## Simplifying `useCallback`

For those already familiar with `useMemo`, you can think of `useCallback` as a simplified version. In essence, `useCallback` is a wrapper around `useMemo` that caches a function.

```jsx
function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}
```

## When to Choose `useMemo` or `useCallback`

In summary, you should choose `useMemo` when you want to cache the result of a function call and ensure it doesn't change unless specific dependencies change. On the other hand, opt for `useCallback` when you want to cache a function itself and prevent it from being recreated on each render, which is particularly useful for event handlers.

By understanding these differences and choosing the appropriate hook for your use case, you can significantly enhance the performance and efficiency of your React applications. Whether you're optimizing data or functions, `useMemo` and `useCallback` are powerful tools at your disposal to achieve that goal.