useCallback keeps a function reference stable between renders until its dependencies change. Its main use is passing callbacks to memoized components: an inline function is a new value on every render, even when its definition looks identical, so it can defeat a memo() comparison. section 3.2.3 section 3.2.4
Listing 3.10 A to-do list with deletable items
import { memo, useCallback, useState } from "react";
const Items = memo(function Items({ items, onDelete }) {
return (
<>
<h2>Todo items</h2>
<ul>
{items.map((todo) => (
<li key={todo}>
{todo}
<button onClick={() => onDelete(todo)}>X</button>
</li>
))}
</ul>
</>
);
});
function Todo() {
const [items, setItems] = useState(["Clean gutter", "Do dishes"]);
const [newItem, setNewItem] = useState("");
const onSubmit = (evt) => {
setItems((items) => items.concat([newItem]));
setNewItem("");
evt.preventDefault();
};
const onChange = (evt) => setNewItem(evt.target.value);
const onDelete = useCallback( #1
(item) => setItems((list) => list.filter((i) => i !== item)),
[] #2
);
return (
<main>
<Items items={items} onDelete={onDelete} />
<form onSubmit={onSubmit}>
<input value={newItem} onChange={onChange} />
<button>Add</button>
</form>
</main>
);
}
function App() {
return <Todo />;
}
export default App;
In the deletable to-do list, Items is wrapped in memo(). The parent gives it an onDelete callback created with useCallback; the callback uses a functional state update, so its dependency array can remain empty. Typing into the separate input no longer gives Items a newly created callback prop. section 3.2.3
Choose dependencies for the values the callback uses. Conceptually, useCallback(fn, deps) is useMemo(() => fn, deps): it memoizes the function rather than a calculated result. A stable callback helps only when its identity matters, such as when it crosses a memoized component boundary. Memoization itself has overhead, so avoid adding it to code that already runs well. section 3.2.4 section 3.2.3
Listing 6.6 The playlist with a memoized callback
import { useReorderable } from "./useReorderable";
import "./playlist.css";
import { ActionType, Song } from "./types";
import { PlaylistItem } from "./PlaylistItem";
import { MouseEvent, useCallback } from "react";
interface PlaylistProps {
songs: Song[];
}
export function Playlist({ songs }: PlaylistProps) {
const [list, dispatch] = useReorderable(songs);
const handleMove = useCallback(
(index: number) => #1
(evt: MouseEvent<HTMLButtonElement>) => { #1
const type = evt.currentTarget.name as ActionType;
dispatch({ type, index });
},
[dispatch]
);
return (
<ol>
{list.map((song, index) => (
<PlaylistItem
key={song.id}
song={song}
index={index}
move={handleMove}
/>
))}
</ol>
);
}
The TypeScript playlist defines handleMove once above the rendered list and passes it to each memoized PlaylistItem. The callback depends on dispatch and explicitly types its index and button event. section 6.1.6
Do not put useCallback inside a list-rendering loop: hooks cannot be called there. Moving the callback above the return also moves it away from the prop that previously supplied TypeScript’s contextual argument types. Type the arguments directly, supply a function type to useCallback, or type the receiving variable. section 6.1.6

