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.
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>
);
}
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 usinguseMemo
on dependencies that change a lot. Even with usinguseMemo
if the dependencies change the recalculations would be performed. In these cases, you should not useuseMemo
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 theuseEffect
hook. TheuseMemo
should only be used for synchronous operations, asynchronous operations should be handled inuseEffect
hook and combination withuseState
.
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