Repository: facebook/react
Pull request: [compiler] Allow nonreactive stable types as extraneous deps


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

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts
patch: @@ -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') {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps-allow-nonreactive-stable-types-as-extra-deps.expect.md
patch: @@ -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 ]]"
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps-allow-nonreactive-stable-types-as-extra-deps.js
patch: @@ -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: [],
+};