memo vs useMemo in React

memo vs useMemo in React

·

11 min read

In this blog post, we will look at the memo and useMemo in React. We will do a deep dive into these performance optimization options offered by React and look at their difference and see in what cases memo and useMemo should be used.

Dead Simple Chat JavaScript Chat API and SDK allow you to add chat to your React application in minutes. Dead Simple Chat is a highly scaleable Chat Platform that can be used for any chat use case.

What is memo in React?

memo is a Higher Order Component in React (If you want to learn about HOCs you can check out our detailed guide on HOC in React) that prevents the re-rendering of the component if its props are unchanged.

What is useMemo in React?

useMemo is a React Hook, that caches the result of a computationally expensive function between re-renders and allows to trigger the method only when the method dependencies changes.

React memo in Action

Let's look at an example under React memo and how we can use it to optimize our components.

import { useEffect, useState } from "react";

const ChildComponent = () => {
  console.log("Child re-rendered");
  return <div>I am a Child Componenet.</div>;
};

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return (
    <div className="App">
      Count: {count}
      <ChildComponent />
    </div>
  );
}

If you look at the example above, we have created a parent component called as App component.

In the App component we are running a setInterval and increment the count value by 1 every second.

If we run this code, you will see "Child re-rendered" continuously printed on the console.

It is because each time the count is updated, the App component is redrawn, thus all the child components are also re-drawn.

To prevent this we can use the memo HOC to create a memoized version of our ChildComponent. As our ChildComponents do not accept any props it is will not be redrawn each time the App component is updated.

click here for example video

Here is the memoized version of our using React Memo:

import { memo, useEffect, useState } from "react";

const ChildComponent = () => {
  console.log("Child re-rendered");
  return <div>I am a Child Component.</div>;
};

const MemoizedChild = memo(ChildComponent);

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return (
    <div className="App">
      Count: {count}
      <MemoizedChild />
    </div>
  );
}

click here for example video

When you will run this code, you can see "Child re-rendered" is printed only twice, and not every time the App component is updated.

But as you have noticed it is printed twice and not once, because memo is performance optimization and not a guarantee.

React will still re-render the memoized version of the component as it sees fit at regular intervals.

Dead Simple Chat's JavaScript Chat API and SDK allow you to easily add custom chat to your React Application.

React useMemo in Action

The useMemo hook is used to optimize the computationally expensive function, that you don't want to call unless the input to the function changes.

Do not use the useMemo hook to call async requests, for that you should use the useEffect hook.

Now let's look at an example of useMemo to better understand its working.

In the example below we have React component called as BlogWriter. The BlogWriter component allows the user enter a title of the blog post, and post body.

It then calculateTextStats method to compute the word count of the blog post body.

import React, { useState } from "react";

const calculateTextStats = (text) => {
  console.log("calculateTextStats called");

  const words = text.trim().length > 0 ? text.trim().split(/\s+/) : [];
  const characters = text.replace(/\s+/g, "").length;

  return {
    wordCount: words.length,
    characterCount: characters,
  };
};

const BlogWriter = () => {
  const [body, setBody] = useState("");
  const [title, setTitle] = useState("");
  const stats = calculateTextStats(body);

  return (
    <div>
      <input
        style={{ marginBottom: "10px" }}
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Post title"
      />
      <br />
      <textarea
        value={body}
        onChange={(e) => setBody(e.target.value)}
        placeholder="Post Body"
      />
      <div>
        <p>Word count: {stats.wordCount}</p>
        <p>Character count: {stats.characterCount}</p>
      </div>
    </div>
  );
};

const App = () => {
  return (
    <div style={{ margin: "20px" }}>
      <h1>Create Post</h1>
      <BlogWriter />
    </div>
  );
};

export default App;

We have added a console.log("calculateTextStats called"); statement in the calculateTextStats method to print on the console each time the method is called.

When you will run this application, you will notice the "calculateTextStats called" will be printed on the console.

We can optimize this function using the useMemo hook. By using the useMemo hook we make sure that the calculateTextStats function is only called when the "Post Body" is updated.

To update our example to use the useMemo hook for optimization we just need to replace the code:

const stats = calculateTextStats(body);

with

  const stats = useMemo(() => {
    return calculateTextStats(body);
  }, [body]);

Here is the complete code example for reference:

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

const calculateTextStats = (text) => {
  console.log("calculateTextStats called");

  const words = text.trim().length > 0 ? text.trim().split(/\s+/) : [];
  const characters = text.replace(/\s+/g, "").length;

  return {
    wordCount: words.length,
    characterCount: characters,
  };
};

const BlogWriter = () => {
  const [body, setBody] = useState("");
  const [title, setTitle] = useState("");

  const stats = useMemo(() => {
    return calculateTextStats(body);
  }, [body]);

  return (
    <div>
      <input
        style={{ marginBottom: "10px" }}
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="Post title"
      />
      <br />
      <textarea
        value={body}
        onChange={(e) => setBody(e.target.value)}
        placeholder="Post Body"
      />
      <div>
        <p>Word count: {stats.wordCount}</p>
        <p>Character count: {stats.characterCount}</p>
      </div>
    </div>
  );
};

const App = () => {
  return (
    <div style={{ margin: "20px" }}>
      <h1>Create Post</h1>
      <BlogWriter />
    </div>
  );
};

export default App;

As you can see in the demo now when we update the "Post title" now the "calculateTextStats called" message is not printed on the screen.

Only when we update the "Post Body" we can see the "calculateTextStats called" message being printed on the screen.

Difference between memo and useMemo

As we have seen with the above examples memo and useMemo have similar names, and different approaches, but both are used to optimize the React components.

The memo is a higher-order component which is used to create an optimized version of a component that is re-rendered only when props change.

Whereas useMemo is a hook which is used to cache the result of an expensive function inside a component, and only calls the expensive function when its dependencies change.

To summarize the difference, the useMemo is used to optimize the computationally expensive operations that run inside the component, whereas memo is used to optimize the rendering of the component itself.

When useMemo should be used?

If your code runs fine without using useMemo then leave it as is. It is not a good idea to be premature optimizations.

But if you notice hangs or lags in your application. If you notice the UI becomes unresponsive then you can investigate the computationally expensive methods in your application, and optimize them using useMemo.

Here are some of the common scenarios that you can look for in your application to optimize using useMemo

Cache the Result of Computationally Expensive Method

You can cache the result of a computationally expensive method so that other UI interactions do not result in the execution of the method.

Check out the example below:

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

const doLargeCalculation = (num) => {
  let result = num;
  for (let i = 0; i < 100000000; i++) {
    result += Math.random();
  }
  return result.toFixed(2);
};

const LargeCalculationComponent = () => {
  const [input, setInput] = useState(0);
  const result = useMemo(() => doLargeCalculation(input), [input]);

  return (
    <div>
      <input placeholder="Do nothing.." type="text" />
      <br />

      <input
        type="number"
        value={input}
        onChange={(e) => setInput(+e.target.value)}
      />
      <p>Result: {result}</p>
    </div>
  );
};

const App = () => {
  return (
    <div style={{ margin: "20px" }}>
      <LargeCalculationComponent />
    </div>
  );
};

export default App;

Check the example above, here updating the text box with place holder will not result in the execution of the doLargeCalculation method.

Caching the Derived State

If we want to cache the value computed using the derived state we can use the useMemo hook to prevent unnecessary calculations.

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

const calculateTotal = (numbers) => {
  return numbers.reduce((acc, val) => acc + val, 0);
};

const AdderComponent = () => {
  const [numbers, setNumbers] = useState([]);
  const [input, setInput] = useState("");

  const total = useMemo(() => calculateTotal(numbers), [numbers]);

  const add = () => {
    setNumbers([...numbers, Number(input)]);
    setInput("");
  };

  return (
    <div>
      <input
        type="number"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Input a number"
      />
      <button onClick={add}>Add</button>
      <p>Tape: {numbers.join(", ")}</p>
      <p>Total: {total}</p>
    </div>
  );
};

const App = () => {
  return (
    <div style={{ margin: "20px" }}>
      <AdderComponent />
    </div>
  );
};

export default App;

In the above code the AdderComponent acts like a tape calculator, it pushed the new numbers into an array and then displays the total.

By using the useMemo hook on the total we ensure that the total calculation is only performed when the numbers array changes.

This prevents unnecessary recalculation of the total when the component re-renders for any reason.

useMemo common pitfalls

useMemo is a powerful optimization tool, but you should be aware of some of the most common pitfalls when using useMemo in your applications:

  • Pre-mature Optimisation: Using useMemo in your application, when it is not needed, is the most common pitfall. This introduces unnecessary complexity in the code and might introduce some unintended behaviour in the application and hard-to-find bugs.

  • Using useMemo on dependencies that change a lot: There is no benefit of using useMemo on dependencies that change a lot. Even with using useMemo if the dependencies change the recalculations would be performed. In these cases, you should not use useMemo as it does not result in any performance benefit.

  • Using useMemo to handle side-effects: Data fetching for storage or API, sending Ajax requests, and manually updating the DOM are all examples of side-effects, all these operations should be performed in the useEffect hook. The useMemo should only be used for synchronous operations, asynchronous operations should be handled in useEffect hook and combination with useState.

When memo should be used?

React memo should be used in cases where the component is re-render too often even though its props are unchanged.

Reducing unnecessary render when Parent Component update

In a scenario where the parent component updates but the props of the child component are unchanged.

In this case, if we wrap the child component in memo then it would prevent the re-rendering of the child component unless the props of the child component also change.

We have seen an example of this in this blog post, but here is another example:

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

const ChildComponent = memo((props) => {
  console.log("Rending Child Component");
  return <div>Counter: {props.count}</div>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [toggle, setToggle] = useState(false);

  return (
    <div>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        Increment
      </button>

      <button onClick={() => setCount((prevCount) => prevCount - 1)}>
        Decrement
      </button>

      <button
        style={{ fontSize: toggle ? "40px" : "12px" }}
        onClick={() => setToggle((prevToggle) => !prevToggle)}
      >
        Toggle
      </button>
      <ChildComponent count={count} />
    </div>
  );
};

const App = () => {
  return (
    <div style={{ margin: "20px" }}>
      <ParentComponent />
    </div>
  );
};

export default App;

In the above code, there are two state variables in the parent component, one is count and another one is toggle.

count is passed as a prop to the child component, and we have memoized the child component, so it will only be re-rendered when its prop that is count changes.

The ChildComponent will not be re-rendered when the toggle state is updated.

Caching Lists

React memo can be used to cache the list items, when a list is frequently updated.

By using memo on the List Items you ensure that the entire list is not re-rendered when an item in the list is updated.

Let's look at an example:

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

const TodoItem = memo(({ item }) => {
  console.log(`Rendering todo item: ${item}`);
  return <li>{item}</li>;
});

const TodoListComponent = () => {
  const [todos, setTodos] = useState(["Buy milk", "Buy eggs", "Drink milk"]);
  const [newTodo, setNewTodo] = useState("");

  const addTodo = () => {
    setTodos([...todos, newTodo]);
    setNewTodo("");
  };

  return (
    <div>
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
      />
      <button onClick={addTodo}>Add Todo</button>
      <ul>
        {todos.map((item) => (
          <TodoItem key={item} item={item} />
        ))}
      </ul>
    </div>
  );
};

const App = () => {
  return (
    <div style={{ margin: "20px" }}>
      <TodoListComponent />
    </div>
  );
};

export default App;

In this example the TodoListComponent manages a list of todos. Each time todo is added a new TodoItem component is rendered on the screen.

By wrapping useMemo around the TodoItem component, when a new Todo is added to the the todos array, all the TodoItems are not re-drawn.

It is especially useful in cases where a large list of todo has to be drawn on the screen. Wrapping each individual item in memo we ensure only the Item that is being updated is redrawn.

In the above video, as I added "Eat Eggs" only the "Eat Eggs" todo was rendered and not the entire list.

memo common Pitfalls

A common pitfall to keep in mind when using React memo is that it does shallow prop-checking, and if your props are functions, array or object the component will re-render each time the parent component renders.

To prevent this you can pass a comparison function the memo HOC. You can find more details about this at the official React Documentation: Specifying a custom comparison function

Keep in mind if are passing the comparison function then you must compare every prop, including functions.

Conclusion

In this blog post, we have seen what is React memo and useMemo. What are the differences between the two, and how we can use them to make our React applications more performant.

We have seen several code examples, and scenarios where to apply memo and useMemo and also some of the common pitfalls.

This article was originally published on the deadsimplechat website: memo vs useMemo in React