Repository: facebook/react
Pull request: [Compiler] Avoid capturing global setStates for no-derived-computations lint


Summary:
This only matters when enableTreatSetIdentifiersAsStateSetters=true

This pattern is still bad. But Right now the validation can only recommend to move stuff to "calculate in render"

A global setState should not be moved to render, not even conditionally and you can't remove state without crossing Component boundaries, which makes this a different kind of fix.

So while we are only suggesting "calculate in render" as a fix we should disallow the lint from throwing in this case IMO

Test Plan:
Added a fixture

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35135).
* __->__ #35135
* #35134

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts
patch: @@ -690,6 +690,18 @@ function validateEffect(
         instr.value.args.length === 1 &&
         instr.value.args[0].kind === 'Identifier'
       ) {
+        const calleeMetadata = context.derivationCache.cache.get(
+          instr.value.callee.identifier.id,
+        );
+
+        /*
+         * If the setState comes from a source other than local state skip
+         * since the fix is not to calculate in render
+         */
+        if (calleeMetadata?.typeOfValue != 'fromState') {
+          continue;
+        }
+
         const argMetadata = context.derivationCache.cache.get(
           instr.value.args[0].identifier.id,
         );

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/from-props-setstate-in-effect-no-error.expect.md
patch: @@ -0,0 +1,65 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
+
+function Component({setParentState, prop}) {
+  useEffect(() => {
+    setParentState(prop);
+  }, [prop]);
+
+  return <div>{prop}</div>;
+}
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
+
+function Component(t0) {
+  const $ = _c(7);
+  const { setParentState, prop } = t0;
+  let t1;
+  if ($[0] !== prop || $[1] !== setParentState) {
+    t1 = () => {
+      setParentState(prop);
+    };
+    $[0] = prop;
+    $[1] = setParentState;
+    $[2] = t1;
+  } else {
+    t1 = $[2];
+  }
+  let t2;
+  if ($[3] !== prop) {
+    t2 = [prop];
+    $[3] = prop;
+    $[4] = t2;
+  } else {
+    t2 = $[4];
+  }
+  useEffect(t1, t2);
+  let t3;
+  if ($[5] !== prop) {
+    t3 = <div>{prop}</div>;
+    $[5] = prop;
+    $[6] = t3;
+  } else {
+    t3 = $[6];
+  }
+  return t3;
+}
+
+```
+
+## Logs
+
+```
+{"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":105},"end":{"line":9,"column":1,"index":240},"filename":"from-props-setstate-in-effect-no-error.ts"},"fnName":"Component","memoSlots":7,"memoBlocks":3,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
+```
+      
+### Eval output
+(kind: exception) Fixture not implemented
\ No newline at end of file

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/from-props-setstate-in-effect-no-error.js
patch: @@ -0,0 +1,9 @@
+// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
+
+function Component({setParentState, prop}) {
+  useEffect(() => {
+    setParentState(prop);
+  }, [prop]);
+
+  return <div>{prop}</div>;
+}

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/usestate-derived-from-prop-no-show-in-data-flow-tree.expect.md
patch: @@ -64,7 +64,6 @@ function Component(t0) {
 ## Logs
 
 ```
-{"kind":"CompileError","detail":{"options":{"description":"Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user\n\nThis setState call is setting a derived value that depends on the following reactive sources:\n\nState: [second]\n\nData Flow Tree:\n└── second (State)\n\nSee: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state","category":"EffectDerivationsOfState","reason":"You might not need an effect. Derive values in render, not effects.","details":[{"kind":"error","loc":{"start":{"line":14,"column":4,"index":443},"end":{"line":14,"column":8,"index":447},"filename":"usestate-derived-from-prop-no-show-in-data-flow-tree.ts","identifierName":"setS"},"message":"This should be computed during render, not in an effect"}]}},"fnLoc":null}
 {"kind":"CompileSuccess","fnLoc":{"start":{"line":3,"column":0,"index":64},"end":{"line":18,"column":1,"index":500},"filename":"usestate-derived-from-prop-no-show-in-data-flow-tree.ts"},"fnName":"Component","memoSlots":5,"memoBlocks":2,"memoValues":3,"prunedMemoBlocks":0,"prunedMemoValues":0}
 ```