[Compiler] Avoid capturing global setStates for no-derived-computations lint (#35135)

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
This commit is contained in:
Jorge Cabiedes
2025-11-13 22:56:06 -08:00
committed by GitHub
parent de97ef9ad5
commit 257b033fc7
4 changed files with 86 additions and 1 deletions

View File

@@ -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,
);

View File

@@ -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

View File

@@ -0,0 +1,9 @@
// @validateNoDerivedComputationsInEffects_exp @enableTreatSetIdentifiersAsStateSetters @loggerTestOnly
function Component({setParentState, prop}) {
useEffect(() => {
setParentState(prop);
}, [prop]);
return <div>{prop}</div>;
}

View File

@@ -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}
```