A parent render normally renders its child components too, even when their properties have not changed. Wrap a component in React’s memo() to reuse its previous result when its properties are unchanged. That can avoid work in the component and its DOM reconciliation, but memoization adds comparison overhead, so it is not a default for every component. section 3.2.1 section 3.4
Keep the to-do list still while typing
A controlled input updates newItem on every keystroke, causing Todo to render. Without memo(), its Items child renders as well, although the items array has not changed. Define const Items = memo(function Items({ items }) { /* render the list */ }); to let typing reuse the list’s previous result. Submitting a to-do calls setItems with a new array, so the list receives a changed property and renders. The unoptimized keypress handler can take up to 5 ms in the browser-performance example; that is a reason to measure this path, not a promised saving from memo(). section 3.2.1
Listing 3.6 A to-do list with memoization
import { memo, useState } from "react"; #1
const Items = memo( #2
function Items({ items }) {
return (
<>
<h2>Todo items</h2>
<ul>
{items.map((todo) => (
<li key={todo}>{todo}</li>
))}
</ul>
</>
);
});
function Todo() {
const [items, setItems] = useState(["Clean gutter", "Do dishes"]);
const [newItem, setNewItem] = useState("");
const onSubmit = (evt) => {
setItems((list) => list.concat([newItem]));
setNewItem("");
evt.preventDefault();
};
const onChange = (evt) => setNewItem(evt.target.value);
return (
<main>
<Items items={items} />
<form onSubmit={onSubmit}>
<input value={newItem} onChange={onChange} />
<button>Add</button>
</form>
</main>
);
}
function App() {
return <Todo />;
}
export default App;
The complete to-do implementation shows memo() around Items and separates input-state updates from changes to the items array. section 3.2.1
Property stability is the practical constraint: memo() compares properties, so a changed identity defeats reuse even when a value looks equivalent. useMemo() and useCallback() address related recalculation and function-identity problems; use their dependency arrays carefully rather than adding memoization everywhere. React’s experimental compiler aims to automate this work, but manual memoization remains relevant while it is not widely adopted. section 3.4 section 3.2
TypeScript adds a separate concern for generic components: wrapping one in memo() can lose its generic type. Because the memoized component has the same intended call signature, cast the result to typeof the original component. A React module augmentation is another option when the fix would otherwise be repeated; it changes the project’s type definitions globally. This typing issue is not fixed in React 19. section 6.2.2
function PaginableWithoutMemo<Type>( ... const Paginable = memo(PaginableWithoutMemo) as typeof PaginableWithoutMemo;
The cast preserves the original generic component’s type after wrapping it in memo(). section 6.2.2
