DEV Community

Cover image for Optimizing React Components: A Guide to useCallback and useMemo
dickinsontiwari
dickinsontiwari

Posted on

Optimizing React Components: A Guide to useCallback and useMemo

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now lets define the shipping component.

// 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

function useCallback(fn, dependencies) {
  return useMemo(() => fn, dependencies);
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (2)

Collapse
 
brense profile image
Rense Bakker

Good explanation! I think it's also worth mentioning that the concept of memoization in React is important, because of how diffing works in the virtual DOM. In essence, if you don't use memoization in your component, you pass everything by value, instead of by reference. The virtual DOM diffing only checks for referential equality, so if you pass things by value (unmemoized) even if it is the same value, your whole DOM tree will always update, leading to many expensive and unnecessary repainting.

Collapse
 
rajaerobinson profile image
Rajae Robinson

Very informative post!

Other performance optimization techniques in Next.js include code splitting and dynamic imports.