Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Performance and memo

React re-renders a component when its state changes, and with it all its children, even if their props did not change. Usually this is fast and unnoticeable. But if a child is heavy, for example a list of a thousand rows, extra re-renders start to slow typing down. The memo function lets React skip re-rendering a component when its props stay the same.

When a component re-renders

ReasonExample
Its state changedsetText was called in this component
Its parent re-renderedAny state in App changed
A context it reads changedThe theme was switched

A change of props is not a reason by itself: props change only because the parent re-rendered.

Finding the slow spot

Install the React Developer Tools browser extension. On the Profiler tab start recording, do the slow action — for example, type into a field — and stop recording. The Profiler shows which components re-rendered and how long it took. Optimize only what is actually slow.

An example problem

In the to-do list, the input and the list live in one component. Every keystroke changes text and re-renders the whole TodoList:

// src/App.jsx
export default function App() {
  const [text, setText] = useState('');
  const [todos, setTodos] = useState(initialTodos);
 
  return (
    <>
      <input value={text} onChange={(event) => setText(event.target.value)} />
      <TodoList todos={todos} onDelete={handleDelete} />
    </>
  );
}

With a dozen tasks it does not matter. With thousands, typing starts to lag.

memo

Wrap the component in memo — in src/components/TodoList.jsx:

// src/components/TodoList.jsx
import { memo } from 'react';
import TodoItem from './TodoItem.jsx';
 
const TodoList = memo(function TodoList({ todos, onDelete }) {
  return (
    <ul>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} onDelete={onDelete} />
      ))}
    </ul>
  );
});
 
export default TodoList;

Now React compares the new props with the old ones and, if they are all equal, skips the re-render. Typing in the field no longer touches the list.

memo and functions in props

Props are compared strictly, with ===. The handleDelete function declared in App is created anew on every render, and memo sees a new prop. That is why it is remembered with useCallback — see the lesson on useMemo and useCallback. The same applies to objects and arrays created right in the markup.

Other approaches

Often a simple rearrangement solves the problem: move the input into its own AddTodoForm component with its own state — then typing re-renders only the form and does not touch App or the list. This is better than memo because no props have to be compared. Very long lists use virtualization — rendering only the visible rows, for example with the react-window library.

Common mistake

memo does not help if a prop gets a new object or function every time:

// Wrong: style and onDelete are new on every render of App.
<TodoList
  todos={todos}
  style={{ margin: 8 }}
  onDelete={(id) => handleDelete(id)}
/>

Move constant objects outside the component and wrap functions in useCallback. Otherwise memo only adds the extra work of comparing props.

Thanks for reading!

Donate to the site — we work hard for you.