useMemo and useCallback keep their result until a value in the dependency list changes. If the callback reads a value that isn't in the list, the result stays the same when that value changes, and the component shows old data. A value in the list that the callback doesn't read makes it run again for nothing.
You don't have to list values that never change between renders, such as a setState function or a ref.
In .ts and .tsx files, the report comes with a suggestion that rewrites the list, which your editor can apply.
Configure
Off by default. To turn it on:
[lint.rules]
"react-hooks/memo-dependencies" = "error" # or "warn"
Examples
Reported, because query is missing from the list:
import { useMemo } from "react";
function Results({ items, query }) {
const visible = useMemo(() => items.filter(item => item.name.includes(query)), [items]);
return <List items={visible} />;
}
Reported, because the callback doesn't read theme:
import { useCallback } from "react";
function SaveButton({ save, draft, theme }) {
const onClick = useCallback(() => save(draft), [save, draft, theme]);
return <button onClick={onClick}>Save</button>;
}
Not reported:
import { useCallback, useMemo, useState } from "react";
function Results({ items, query }) {
const [selected, setSelected] = useState(null);
const visible = useMemo(() => items.filter(item => item.name.includes(query)), [items, query]);
const onSelect = useCallback(item => setSelected(item.id), []);
return <List items={visible} selected={selected} onSelect={onSelect} />;
}
When not to use it
react-hooks/exhaustive-deps checks the same two hooks, and effects too. With both rules on, the same list is reported twice.