unstable-exhaustive-deps
Verifies the list of dependencies for Hooks like `useEffect` and similar, with support for custom Hooks and stable Hook results.
This rule is experimental and may change in the future or be removed. Its options may not be compatible with a future upstream solution in eslint-plugin-react-hooks.
Full Name in eslint-plugin-react-x
react-x/unstable-exhaustive-depsFull Name in @eslint-react/eslint-plugin
@eslint-react/unstable-exhaustive-depsFeatures
โ๏ธ ๐ง ๐งช
Rule Details
This rule is a fork of exhaustive-deps with an additional hooks option, which lets you configure:
- Custom Hooks whose dependencies should be checked, with the callback and the dependencies array at any position.
- Custom Hooks that return stable values (like the setter returned by
useState), so those values are not required as dependencies.
Without the hooks option, it reports the same problems as exhaustive-deps. Enable one of the two rules, not both.
The hooks option is based on the hooks option of Biome's useExhaustiveDependencies rule. It currently supports name, closureIndex, dependenciesIndex, and the boolean | number | number[] forms of stableResult. Biome's string[] form for stable object properties is not supported yet.
Examples
Custom Hooks with a dependencies array
{
"react-x/unstable-exhaustive-deps": ["warn", {
"hooks": [
{ "name": "useCustomEffect", "closureIndex": 0, "dependenciesIndex": 1 },
{ "name": "useQuery", "closureIndex": 2, "dependenciesIndex": 1 }
]
}]
}// ๐ด Problem: 'userId' is missing from the dependency arrays
function MyComponent({ userId }) {
useCustomEffect(() => {
sendAnalytics(userId);
}, []);
const user = useQuery("user", [], () => fetchUser(userId));
}// ๐ข Recommended: Include all dependencies
function MyComponent({ userId }) {
useCustomEffect(() => {
sendAnalytics(userId);
}, [userId]);
const user = useQuery("user", [userId], () => fetchUser(userId));
}Custom Hooks with stable results
{
"react-x/unstable-exhaustive-deps": ["warn", {
"hooks": [
{ "name": "useDispatch", "stableResult": true },
{ "name": "useRecoilState", "stableResult": [1] }
]
}]
}// ๐ข Recommended: 'dispatch' and 'setCount' are stable, so they don't need to be listed
function MyComponent() {
const dispatch = useDispatch();
const [count, setCount] = useRecoilState(countState);
const reset = useCallback(() => {
dispatch(resetAction());
setCount(0);
}, []);
}// ๐ด Problem: 'count' is not stable, so it is missing from the dependency array
function MyComponent() {
const [count, setCount] = useRecoilState(countState);
useEffect(() => {
setCount(count + 1);
}, []);
}Options
Rule Options
This rule accepts an optional configuration object:
type Options = {
additionalHooks?: string;
enableDangerousAutofixThisMayCauseInfiniteLoops?: boolean;
hooks?: {
name: string;
closureIndex?: number;
dependenciesIndex?: number;
stableResult?: boolean | number | number[];
}[];
};hooks
A list of custom Hooks. Each entry accepts the following properties:
name: The name of the Hook. It matches calls likeuseQuery()andlib.useQuery(). Only dot access is supported; computed member access is not resolved.closureIndex: The position of the callback, starting from zero (ex:0foruseEffect).dependenciesIndex: The position of the dependencies array, starting from zero (ex:1foruseEffect).stableResult: Marks the values returned by the Hook as stable, so they are not required as dependencies:true: The returned value is stable (ex:useRef).- A position, or an array of positions: The values at those positions of the returned array are stable (ex:
1or[1]foruseState). - Biome's
string[]form for stable object properties is not supported yet.
closureIndex and dependenciesIndex must be set together, and the Hook is only checked when they are different. A Hook configured this way is checked at these positions even if it is a built-in Hook or matches additionalHooks.
additionalHooks
Same as the additionalHooks option of exhaustive-deps. Hooks matched by it are checked with the callback as the first argument and the dependencies array as the second argument.
enableDangerousAutofixThisMayCauseInfiniteLoops
Same as the enableDangerousAutofixThisMayCauseInfiniteLoops option of exhaustive-deps.
Default: false
Shared Settings
Like exhaustive-deps, this rule reads custom effect Hooks from the additionalEffectHooks shared setting when additionalHooks is not set:
{
"settings": {
"react-x": {
"additionalEffectHooks": "/^(useMyEffect|useCustomEffect)$/u"
}
}
}