Exploring React Hooks: useMemo

Exploring React Hooks: useMemo

Welcome back to our ongoing Exploring React Hooks series! In the previous articles, we explored the useCallback Hook. Today, we'll focus our attention on the useMemo hook, which allows us to optimize the performance of our React components.

Lets start exploring!

Memoization:

Memoization is an optimization technique that caches function results based on inputs. When a function is called with the same set of arguments multiple times, instead of executing the function each time, we can return the cached result. This helps in reducing redundant calculations and improves performance.

🪝What is the useMemo Hook?

The useMemo Hook is closely related to the useCallback Hook we previously explored. While useCallback optimizes the creation of memoized callback functions, useMemo optimizes the creation of memoized values.

The useMemo hook is a powerful tool provided by React that allows us to memoize the result of a function and cache it for future use.

By memoizing expensive computations and function calls, useMemo allows us to avoid unnecessary recalculations and enhances the overall performance of our components.

Syntax:

const memoizedValue = useMemo(() => {
  // Expensive computation or function
  return computedValue;
}, [dependency]);

The useMemo hook takes two arguments:

  • callback function: contains the expensive computation or function that needs to be memoized.

  • dependency array: specifies the variables on which the memoization depends.

When any of the variables in the dependency array change, the memoized value is recomputed. If none of the variables change, the previously cached value is returned.

🪝Scenario

Let's consider a scenario in which there are two counters, where one counter involves a time-consuming computation.

First, let's create a component called Counter.

We'll import the useState hook from React to create two state variables: counterOne and counterTwo, both initialized with a value of 0.

Next, let's create two functions within the component: incrementOne and incrementTwo. We'll be using these functions to update the respective counters by incrementing their values when the corresponding buttons are clicked. We will use the setCounterOne and setCounterTwo functions provided by the useState hook to update the state variables.

Additionally, let's create a function called isEven. We'll use this function to check if the value of counterOne is even or odd. Within this function, we'll include a time-consuming loop where i increments until it reaches a very large number (2,000,000,000 in this case). This loop will serve as an example of an expensive computation for demonstration purposes.

import React, { useState } from "react";

function Counter() {
  const [counterOne, setCounterOne] = useState(0);
  const [counterTwo, setCounterTwo] = useState(0);

  const incrementOne = () => {
    setCounterOne(counterOne + 1);
  };

  const incrementTwo = () => {
    setCounterTwo(counterTwo + 1);
  };

  const isEven = () => {
    let i = 0;
    while (i < 2000000000) i++;
    return counterOne % 2 === 0;
  };

  return (
    <div>
      <div>
        <button onClick={incrementOne}>
          Increment Counter One - {counterOne}
        </button>
        <span>{isEven() ? "Even" : "Odd"}</span>
      </div>
      <div>
        <button onClick={incrementTwo}>
          Increment Counter Two - {counterTwo}
        </button>
      </div>
    </div>
  );
}

export default Counter;

Inside the return statement, we'll include two div elements, each containing a button and the current value of the corresponding counter. We'll also include a span element that will display the text "Even" or "Odd" based on the result of calling the isEven function.

Clicking the Increment Counter One button will increment counterOne by 1, and the updated value will be displayed next to the button. If the updated value is even, the adjacent text will show "Even"; otherwise, it will show "Odd".

Clicking the Increment Counter Two button will similarly increment counterTwo by 1, and its updated value will be displayed next to the button.

🪝Scenario: Problem

If you notice, clicking on the Increment Counter One button results in a noticeable delay of a few seconds before the UI updates. This delay is obvious, as it is caused by the slow execution of the isEven function, and this, in turn, impacts the rendering performance.

However, even when the Increment Counter Two button is clicked, a comparable delay is observed. Why?

This delay arises because, with each state update and subsequent component re-rendering, the isEven function is invoked once again. As a result, the Increment Counter Two functionality is also affected by the slow performance impact of the functionality of the Increment Counter One.

In short, due to the slow execution of the isEven function and the re-rendering of the component on state updates, both buttons experience delays in updating the UI.

🪝Scenario: Solution

To optimize performance in our scenario, we want to avoid unnecessary calculations of whether counterOne is even or odd when we are only changing the value of counterTwo.

By using the useMemo hook, we can memoize the result of the isEven function and specify that it should be recomputed only when counterOne (the dependency) changes.

This optimization will help eliminate redundant calculations and delays during the rendering of the Increment Counter Two button, enhancing overall performance.

🪝Scenario: Using useMemo

Let's get back to our Counter component.

Here, we will be using the useMemo hook to memoize a computation.

Let's provide the second argument [counterOne] as the dependency array. This will specify that the memoized value should be recalculated whenever counterOne changes. If counterOne remains the same, the previously memoized value will be returned without recomputation.

import React, { useState, useMemo } from "react";

function Counter() {
  const [counterOne, setCounterOne] = useState(0);
  const [counterTwo, setCounterTwo] = useState(0);

  const incrementOne = () => {
    setCounterOne(counterOne + 1);
  };

  const incrementTwo = () => {
    setCounterTwo(counterTwo + 1);
  };

  const isEven = useMemo(() => {
    let i = 0;
    while (i < 2000000000) i++;
    return counterOne % 2 === 0;
  }, [counterOne]);

  return (
    <div>
      <div>
        <button onClick={incrementOne}>
          Increment Counter One - {counterOne}
        </button>
        <span>{isEven ? "Even" : "Odd"}</span>
      </div>
      <div>
        <button onClick={incrementTwo}>
          Increment Counter Two - {counterTwo}
        </button>
      </div>
    </div>
  );
}

export default Counter;

Now, if you click on the Increment Counter One button, the expected delay is still noticeable.

However, now, if you click on the Increment Counter Two Button, updates occur significantly faster.

This improvement is due to React utilizing the cached value of the isEven function, eliminating the need for recomputation when the value of Increment Counter One remains unchanged. By leveraging cached values from previous renders, React optimizes performance and speeds up updates.

Output:

Import the Counter component into your App Component and run the server.

We just demonstrated the useMemo hook to memoize the computation ofcounterOne. By memoizing the isEven value, the expensive computation is performed only when necessary, avoiding unnecessary recalculations during subsequent render when counterOne remains the same.

🪝Benefits of useMemo:

  1. Performance Optimization:

    By memoizing expensive computations, we can prevent unnecessary re-calculations, leading to improved performance and a smoother user experience.

  2. Avoiding Unnecessary Renders:

    When a memoized value remains unchanged, React avoids re-rendering components that depend on it. This reduces render cycles and enhances the overall efficiency of your application.

  3. Precise Dependency Control:

    The dependency array in useMemo allows us to specify exactly which variables should trigger the recalculation. This level of control ensures that only relevant changes trigger the memoization process.

To Summarize:

To summarize, the useMemo hook in React is a powerful tool for optimizing performance that allows memoizing expensive computations or function results. By caching and reusing values, it minimizes redundant calculations, leading to improved efficiency.

With the ability to specify dependencies, useMemo ensures that memoized values are only recomputed when necessary, reducing render cycles and enhancing overall performance in React applications.


References


Thank you for taking the time to read this blog. Hope you found it informative and enjoyable!

Share your thoughts and connect with me on Twitter.

Stay tuned for our last article of the series: Custom Hooks.

Catch you guys on the next one. Cheers .. ✌️

Did you find this article valuable?

Support Raj Sarkar by becoming a sponsor. Any amount is appreciated!