[compiler] Allow nonreactive stable types as extraneous deps (#35185)

When checking ValidateExhaustiveDeps internally, this seems to be the
most common case that it flags. The current exhaustive-deps rule allows
extraneous deps if they are a set of stable types. So here we reuse our
existing isStableType() util in the compiler to allow this case.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35185).
* #35201
* #35202
* #35192
* #35190
* #35186
* __->__ #35185
This commit is contained in:
Joseph Savona
2025-11-24 12:15:06 -08:00
committed by GitHub
parent fca172e3f3
commit c9a8cf3411
3 changed files with 156 additions and 0 deletions

View File

@@ -261,6 +261,20 @@ export function validateExhaustiveDependencies(
extra.push(dep);
}
/*
* For compatiblity with the existing exhaustive-deps rule, we allow
* known-stable values as dependencies even if the value is not reactive.
* This allows code that takes a dep on a non-reactive setState function
* to pass, for example.
*/
retainWhere(extra, dep => {
const isNonReactiveStableValue =
dep.root.kind === 'NamedLocal' &&
!dep.root.value.reactive &&
isStableType(dep.root.value.identifier);
return !isNonReactiveStableValue;
});
if (missing.length !== 0 || extra.length !== 0) {
let suggestions: Array<CompilerSuggestion> | null = null;
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {

View File

@@ -0,0 +1,100 @@
## Input
```javascript
// @validateExhaustiveMemoizationDependencies
import {
useCallback,
useTransition,
useState,
useOptimistic,
useActionState,
useRef,
useReducer,
} from 'react';
function useFoo() {
const [s, setState] = useState();
const ref = useRef(null);
const [t, startTransition] = useTransition();
const [u, addOptimistic] = useOptimistic();
const [v, dispatch] = useReducer(() => {}, null);
const [isPending, dispatchAction] = useActionState(() => {}, null);
return useCallback(() => {
dispatch();
startTransition(() => {});
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
}, [
// intentionally adding unnecessary deps on nonreactive stable values
// to check that they're allowed
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies
import {
useCallback,
useTransition,
useState,
useOptimistic,
useActionState,
useRef,
useReducer,
} from "react";
function useFoo() {
const $ = _c(1);
const [, setState] = useState();
const ref = useRef(null);
const [, startTransition] = useTransition();
const [, addOptimistic] = useOptimistic();
const [, dispatch] = useReducer(_temp, null);
const [, dispatchAction] = useActionState(_temp2, null);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
dispatch();
startTransition(_temp3);
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
};
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
function _temp3() {}
function _temp2() {}
function _temp() {}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"

View File

@@ -0,0 +1,42 @@
// @validateExhaustiveMemoizationDependencies
import {
useCallback,
useTransition,
useState,
useOptimistic,
useActionState,
useRef,
useReducer,
} from 'react';
function useFoo() {
const [s, setState] = useState();
const ref = useRef(null);
const [t, startTransition] = useTransition();
const [u, addOptimistic] = useOptimistic();
const [v, dispatch] = useReducer(() => {}, null);
const [isPending, dispatchAction] = useActionState(() => {}, null);
return useCallback(() => {
dispatch();
startTransition(() => {});
addOptimistic();
setState(null);
dispatchAction();
ref.current = true;
}, [
// intentionally adding unnecessary deps on nonreactive stable values
// to check that they're allowed
dispatch,
startTransition,
addOptimistic,
setState,
dispatchAction,
ref,
]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};