1. Memoizing Component with memo
Table of contents
In order to understand difference between memo
, useMemo
and useCallback
, we will take example of todo-list.
Initial Setup
you can clone this codesandbox and skip to the problem in our app or create your own create-react-app with typescript and copy following files to it.
Note:
For anyone who is using react 18 versions, take a look at this before moving forward.
let's start with types.ts
file, which just contains all the types which are used by multiple file in our app.
// types.ts
export type Todo = {
id: number,
task: string,
};
Task.tsx
file contains Task
component which just renders each task in our todo app.
// Task.tsx
import { useEffect } from "react";
import { Todo } from "./types";
export default function Task({ id, task }: Todo) {
useEffect(() => {
console.log("Rendering <Task />", task);
});
return <li>{task}</li>;
}
TodoList.tsx
file contains TodoList
component which renders a list of tasks with the help of the Task
component.
// TodoList.tsx
import { useEffect } from "react";
import { Todo } from "./types";
import Task from "./Task";
export type Props = {
todoList: Array<Todo>,
};
export default function TodoList({ todoList }: Props) {
useEffect(() => {
console.log("Rendering <List />");
});
return (
<ul>
{todoList.length ? (
todoList.map(({ id, task }: Todo) => {
return <Task key={id} id={id} task={task} />;
})
) : (
<h1>There are no tasks in the todo list</h1>
)}
</ul>
);
}
At last we have App.tsx
file we creates task and renders TodoList
component with some initial tasks predefined and function to create a new task.
// App.tsx
import { useState, useEffect } from "react";
import { Todo } from "./types";
import TodoList from "./TodoList";
const initialTodos = [
{ id: 1, task: "Complete i3 setup dotfiles" },
{ id: 2, task: "Fix a bug in a project" },
];
export default function App() {
const [todoList, setTodoList] = useState <Array<Todo>>(initialTodos);
const [task, setTask] = useState("");
useEffect(() => {
console.log("Rendering <App />");
});
const handleCreate = () => {
const newTodo: Todo = { id: Date.now(), task };
setTodoList([...todoList, newTodo]);
setTask("");
};
return (
<>
<input
type="text"
value={task}
onChange={(e) => setTask(e.target.value)}
/>
<button onClick={handleCreate}>Create</button>
<TodoList todoList={todoList} />
</>
);
}
Problem in our app
All Three Components App
, TodoList
and Task
contains a useEffect
without dependency array which keep track of the their renders.
After the initial setup, you should be able to see the following output on the console.
Now, if we try to write a new task in the input, we can see that for each letter we write, those renders appear again for each letter. by just typing "fo", we have three two more batches of renders.
so we can clearly determine that App
Component does not have good performance, this is where memo
can help use to improve performance. in the next sections we're going to learn how to implement memo
, useMemo
, useCallback
to memoize a component, a value and a function.
Memoizing a Component with memo
memo
is a higher order component similar to PureComponent
of React because it performs shallow comparison of props because of this if we pass same props all the the time, component is render once and will memoized. only way to re-render a component is when the prop changes its value.
As you might already figured out, this can be a possible solution for additional re-rendering of our TodoList
and Task
components.
To add memo
, we just need to import it from react and pass the component that we want to memoized.
// TodoList.tsx
import { useEffect, memo } from "react";
...
export default memo(TodoList);
// Task.tsx
import { useEffect, memo } from "react";
...
export default memo(Task);
now, if we write 'fo' again in the input, we get following
Now, we get first batch of renders and 2 extra renders of App
component, which is totally fine because we typed 'fo'.
Also, try creating a new task, here we're adding "complete assignment" as a new task and we got following.
Only App
component rendered 20 times(1 initial + 19 characters of "complete assignment" including space) because we have useState
for creating new task in App
component.
With this you might wonder that we should use memo
with every component?, answer is no because of the performance, if you use memo
everytime then a process of shallow comparison and memoization have inferior performance compared to one if we don't use it.
rule of thumb for using memo
is just don't use it, unless you're working with large amount of data from API or your component needs to perform a lot of renders(e.g in a huge list) or your app is going slow.
Source:
for reference, here is the code we have so far.
Subscribe to my newsletter
Read articles from Jugal Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by