From 663ddab59670ded1b99af0811a11e5e545d85ee3 Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Mon, 27 Oct 2025 13:39:13 -0700 Subject: [PATCH 1/2] [compiler] Prevent overriding a derivationEntry on effect mutation and instead update typeOfValue and fix infinite loops Summary: With this we are now comparing a snapshot of the derivationCache with the new changes every time we are done recording the derivations happening in the HIR. We have to do this after recording everything since we still do some mutations on the cache when recording mutations. Test Plan: Test the following in playground: ``` // @validateNoDerivedComputationsInEffects_exp function Component({ value }) { const [checked, setChecked] = useState(''); useEffect(() => { setChecked(value === '' ? [] : value.split(',')); }, [value]); return (
{checked}
) } ``` This no longer causes an infinite loop. Added a test case in the next PR in the stack --- ...idateNoDerivedComputationsInEffects_exp.ts | 74 +++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts index a755d0e2c65fb..e30c9e8581e6d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts @@ -47,6 +47,43 @@ type ValidationContext = { class DerivationCache { hasChanges: boolean = false; cache: Map = new Map(); + private previousCache: Map | null = null; + + takeSnapshot(): void { + this.previousCache = new Map(); + for (const [key, value] of this.cache.entries()) { + this.previousCache.set(key, { + place: value.place, + sourcesIds: new Set(value.sourcesIds), + typeOfValue: value.typeOfValue, + }); + } + } + + checkForChanges(): void { + if (this.previousCache === null) { + this.hasChanges = true; + return; + } + + for (const [key, value] of this.cache.entries()) { + const previousValue = this.previousCache.get(key); + if ( + previousValue === undefined || + !this.isDerivationEqual(previousValue, value) + ) { + this.hasChanges = true; + return; + } + } + + if (this.cache.size !== this.previousCache.size) { + this.hasChanges = true; + return; + } + + this.hasChanges = false; + } snapshot(): boolean { const hasChanges = this.hasChanges; @@ -92,14 +129,7 @@ class DerivationCache { newValue.sourcesIds.add(derivedVar.identifier.id); } - const existingValue = this.cache.get(derivedVar.identifier.id); - if ( - existingValue === undefined || - !this.isDerivationEqual(existingValue, newValue) - ) { - this.cache.set(derivedVar.identifier.id, newValue); - this.hasChanges = true; - } + this.cache.set(derivedVar.identifier.id, newValue); } private isDerivationEqual( @@ -175,7 +205,6 @@ export function validateNoDerivedComputationsInEffects_exp( sourcesIds: new Set([param.identifier.id]), typeOfValue: 'fromProps', }); - context.derivationCache.hasChanges = true; } } } else if (fn.fnType === 'Component') { @@ -186,12 +215,13 @@ export function validateNoDerivedComputationsInEffects_exp( sourcesIds: new Set([props.identifier.id]), typeOfValue: 'fromProps', }); - context.derivationCache.hasChanges = true; } } let isFirstPass = true; do { + context.derivationCache.takeSnapshot(); + for (const block of fn.body.blocks.values()) { recordPhiDerivations(block, context); for (const instr of block.instructions) { @@ -199,6 +229,7 @@ export function validateNoDerivedComputationsInEffects_exp( } } + context.derivationCache.checkForChanges(); isFirstPass = false; } while (context.derivationCache.snapshot()); @@ -331,11 +362,24 @@ function recordInstructionDerivations( case Effect.ConditionallyMutateIterator: case Effect.Mutate: { if (isMutable(instr, operand)) { - context.derivationCache.addDerivationEntry( - operand, - sources, - typeOfValue, - ); + if (context.derivationCache.cache.has(operand.identifier.id)) { + const operandMetadata = context.derivationCache.cache.get( + operand.identifier.id, + ); + + if (operandMetadata !== undefined) { + operandMetadata.typeOfValue = joinValue( + typeOfValue, + operandMetadata.typeOfValue, + ); + } + } else { + context.derivationCache.addDerivationEntry( + operand, + sources, + typeOfValue, + ); + } } break; } From 3b5087b4c92a49fa2baeae6040314301823095ee Mon Sep 17 00:00:00 2001 From: Jorge Cabiedes Acosta Date: Tue, 28 Oct 2025 16:04:52 -0700 Subject: [PATCH 2/2] [compiler] Fix false negatives and add data flow tree to compiler error for `no-deriving-state-in-effects` Summary: Revamped the derivationCache graph. This fixes a bunch of bugs where sometimes we fail to track from which props/state we derived values from. Also, it is more intuitive and allows us to easily implement a Data Flow Tree. We can print this tree which gives insight on how the data is derived and should facilitate error resolution in complicated components Test Plan: Added a test case where we were failing to track derivations. Also updated the test cases with the new error containing the data flow tree --- ...idateNoDerivedComputationsInEffects_exp.ts | 172 +++++++++++++----- ...ed-state-conditionally-in-effect.expect.md | 11 +- ...derived-state-from-default-props.expect.md | 11 +- ...state-from-local-state-in-effect.expect.md | 11 +- ...-local-state-and-component-scope.expect.md | 13 +- ...d-state-from-prop-setter-ternary.expect.md | 48 +++++ ....derived-state-from-prop-setter-ternary.js | 11 ++ ...state-from-prop-with-side-effect.expect.md | 11 +- ...ect-contains-local-function-call.expect.md | 11 +- ...id-derived-computation-in-effect.expect.md | 11 +- ...erived-state-from-computed-props.expect.md | 12 +- ...ed-state-from-destructured-props.expect.md | 11 +- 12 files changed, 281 insertions(+), 52 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts index e30c9e8581e6d..5b933c4e72d29 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoDerivedComputationsInEffects_exp.ts @@ -102,31 +102,24 @@ class DerivationCache { typeOfValue: typeOfValue ?? 'ignored', }; - if (sourcesIds !== undefined) { - for (const id of sourcesIds) { - const sourcePlace = this.cache.get(id)?.place; + if (isNamedIdentifier(derivedVar)) { + newValue.sourcesIds.add(derivedVar.identifier.id); + } - if (sourcePlace === undefined) { - continue; - } + for (const id of sourcesIds) { + const sourceMetadata = this.cache.get(id); - /* - * If the identifier of the source is a promoted identifier, then - * we should set the target as the source. - */ - if ( - sourcePlace.identifier.name === null || - sourcePlace.identifier.name?.kind === 'promoted' - ) { - newValue.sourcesIds.add(derivedVar.identifier.id); - } else { - newValue.sourcesIds.add(sourcePlace.identifier.id); - } + if (sourceMetadata === undefined) { + continue; } - } - if (newValue.sourcesIds.size === 0) { - newValue.sourcesIds.add(derivedVar.identifier.id); + if (isNamedIdentifier(sourceMetadata.place)) { + newValue.sourcesIds.add(sourceMetadata.place.identifier.id); + } else { + for (const sourcesSourceId of sourceMetadata.sourcesIds) { + newValue.sourcesIds.add(sourcesSourceId); + } + } } this.cache.set(derivedVar.identifier.id, newValue); @@ -151,6 +144,12 @@ class DerivationCache { } } +function isNamedIdentifier(place: Place): boolean { + return ( + place.identifier.name !== null && place.identifier.name?.kind === 'named' + ); +} + /** * Validates that useEffect is not used for derived computations which could/should * be performed in render. @@ -202,7 +201,9 @@ export function validateNoDerivedComputationsInEffects_exp( if (param.kind === 'Identifier') { context.derivationCache.cache.set(param.identifier.id, { place: param, - sourcesIds: new Set([param.identifier.id]), + sourcesIds: new Set( + isNamedIdentifier(param) ? [param.identifier.id] : [], + ), typeOfValue: 'fromProps', }); } @@ -212,7 +213,9 @@ export function validateNoDerivedComputationsInEffects_exp( if (props != null && props.kind === 'Identifier') { context.derivationCache.cache.set(props.identifier.id, { place: props, - sourcesIds: new Set([props.identifier.id]), + sourcesIds: new Set( + isNamedIdentifier(props) ? [props.identifier.id] : [], + ), typeOfValue: 'fromProps', }); } @@ -293,6 +296,7 @@ function recordInstructionDerivations( if (value.kind === 'FunctionExpression') { context.functions.set(lvalue.identifier.id, value); for (const [, block] of value.loweredFunc.func.body.blocks) { + recordPhiDerivations(block, context); for (const instr of block.instructions) { recordInstructionDerivations(instr, context, isFirstPass); } @@ -341,9 +345,7 @@ function recordInstructionDerivations( } typeOfValue = joinValue(typeOfValue, operandMetadata.typeOfValue); - for (const id of operandMetadata.sourcesIds) { - sources.add(id); - } + sources.add(operand.identifier.id); } if (typeOfValue === 'ignored') { @@ -411,6 +413,68 @@ function recordInstructionDerivations( } } +function buildDataFlowTree( + sourceId: IdentifierId, + indent: string = '', + isLast: boolean = true, + context: ValidationContext, + propsSet: Set, + stateSet: Set, +): string { + const sourceMetadata = context.derivationCache.cache.get(sourceId); + if (!sourceMetadata || !sourceMetadata.place.identifier.name?.value) { + return ''; + } + + const sourceName = sourceMetadata.place.identifier.name.value; + const prefix = indent + (isLast ? '└── ' : '├── '); + const childIndent = indent + (isLast ? ' ' : '│ '); + + const childSourceIds = Array.from(sourceMetadata.sourcesIds).filter( + id => id !== sourceId, + ); + + const isOriginal = childSourceIds.length === 0; + + let result = `${prefix}${sourceName}`; + + if (isOriginal) { + let typeLabel: string; + if (sourceMetadata.typeOfValue === 'fromProps') { + propsSet.add(sourceMetadata.place.identifier.name?.value); + typeLabel = 'Prop'; + } else if (sourceMetadata.typeOfValue === 'fromState') { + stateSet.add(sourceMetadata.place.identifier.name?.value); + typeLabel = 'State'; + } else { + propsSet.add(sourceMetadata.place.identifier.name?.value); + stateSet.add(sourceMetadata.place.identifier.name?.value); + typeLabel = 'Prop and State'; + } + result += ` (${typeLabel})`; + } + + if (childSourceIds.length > 0) { + result += '\n'; + childSourceIds.forEach((childId, index) => { + const childTree = buildDataFlowTree( + childId, + childIndent, + index === childSourceIds.length - 1, + context, + propsSet, + stateSet, + ); + if (childTree) { + result += childTree + '\n'; + } + }); + result = result.slice(0, -1); + } + + return result; +} + function validateEffect( effectFunction: HIRFunction, context: ValidationContext, @@ -513,27 +577,49 @@ function validateEffect( .length - 1 ) { - const derivedDepsStr = Array.from(derivedSetStateCall.sourceIds) - .map(sourceId => { - const sourceMetadata = context.derivationCache.cache.get(sourceId); - return sourceMetadata?.place.identifier.name?.value; - }) - .filter(Boolean) - .join(', '); - - let description; - - if (derivedSetStateCall.typeOfValue === 'fromProps') { - description = `From props: [${derivedDepsStr}]`; - } else if (derivedSetStateCall.typeOfValue === 'fromState') { - description = `From local state: [${derivedDepsStr}]`; - } else { - description = `From props and local state: [${derivedDepsStr}]`; + const allSourceIds = Array.from(derivedSetStateCall.sourceIds); + const propsSet = new Set(); + const stateSet = new Set(); + + const trees = allSourceIds + .map((id, index) => + buildDataFlowTree( + id, + '', + index === allSourceIds.length - 1, + context, + propsSet, + stateSet, + ), + ) + .filter(Boolean); + + const propsArr = Array.from(propsSet); + const stateArr = Array.from(stateSet); + + let rootSources = ''; + if (propsArr.length > 0) { + rootSources += `Props: [${propsArr.join(', ')}]`; } + if (stateArr.length > 0) { + if (rootSources) rootSources += '\n'; + rootSources += `State: [${stateArr.join(', ')}]`; + } + + const description = `Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +${rootSources} + +Data Flow Tree: +${trees.join('\n')} + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state`; context.errors.pushDiagnostic( CompilerDiagnostic.create({ - description: `Derived values (${description}) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user`, + description: description, category: ErrorCategory.EffectDerivationsOfState, reason: 'You might not need an effect. Derive values in render, not effects.', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.expect.md index 1fa7f7d7950e6..52074295ffaa9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.expect.md @@ -34,7 +34,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props: [value]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [value] + +Data Flow Tree: +└── value (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.derived-state-conditionally-in-effect.ts:9:6 7 | useEffect(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.expect.md index f30235a064a94..a8ec5240c8f1c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.expect.md @@ -31,7 +31,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props: [input]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [input] + +Data Flow Tree: +└── input (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.derived-state-from-default-props.ts:9:4 7 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-local-state-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-local-state-in-effect.expect.md index 779ddafc40184..59a9e8845b0ef 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-local-state-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-local-state-in-effect.expect.md @@ -28,7 +28,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From local state: [count]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +State: [count] + +Data Flow Tree: +└── count (State) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.derived-state-from-local-state-in-effect.ts:10:6 8 | useEffect(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-local-state-and-component-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-local-state-and-component-scope.expect.md index 7b27b556b3e98..919bf067ba526 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-local-state-and-component-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-local-state-and-component-scope.expect.md @@ -38,7 +38,18 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props and local state: [firstName, lastName]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [firstName] +State: [lastName] + +Data Flow Tree: +├── firstName (Prop) +└── lastName (State) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.derived-state-from-prop-local-state-and-component-scope.ts:11:4 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.expect.md new file mode 100644 index 0000000000000..3d901ff48ff4c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.expect.md @@ -0,0 +1,48 @@ + +## Input + +```javascript +// @validateNoDerivedComputationsInEffects_exp + +function Component({value}) { + const [checked, setChecked] = useState(''); + + useEffect(() => { + setChecked(value === '' ? [] : value.split(',')); + }, [value]); + + return
{checked}
; +} + +``` + + +## Error + +``` +Found 1 error: + +Error: You might not need an effect. Derive values in render, not effects. + +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [value] + +Data Flow Tree: +└── value (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. + +error.derived-state-from-prop-setter-ternary.ts:7:4 + 5 | + 6 | useEffect(() => { +> 7 | setChecked(value === '' ? [] : value.split(',')); + | ^^^^^^^^^^ This should be computed during render, not in an effect + 8 | }, [value]); + 9 | + 10 | return
{checked}
; +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.js new file mode 100644 index 0000000000000..afd198caa262e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-setter-ternary.js @@ -0,0 +1,11 @@ +// @validateNoDerivedComputationsInEffects_exp + +function Component({value}) { + const [checked, setChecked] = useState(''); + + useEffect(() => { + setChecked(value === '' ? [] : value.split(',')); + }, [value]); + + return
{checked}
; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.expect.md index 7fadae5667fdb..370d7f313075a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.expect.md @@ -31,7 +31,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props: [value]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [value] + +Data Flow Tree: +└── value (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.derived-state-from-prop-with-side-effect.ts:8:4 6 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.expect.md index aec543fcbf48c..9fe34e01a50cb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.expect.md @@ -35,7 +35,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props: [propValue]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [propValue] + +Data Flow Tree: +└── propValue (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.effect-contains-local-function-call.ts:12:4 10 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.expect.md index f1f755adfa803..5714131c0d756 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.expect.md @@ -33,7 +33,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From local state: [firstName]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +State: [firstName] + +Data Flow Tree: +└── firstName (State) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.invalid-derived-computation-in-effect.ts:11:4 9 | const [fullName, setFullName] = useState(''); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.expect.md index 3a0788969397a..939de631f97c4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.expect.md @@ -31,7 +31,17 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props: [props]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [props] + +Data Flow Tree: +└── computed + └── props (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.invalid-derived-state-from-computed-props.ts:9:4 7 | useEffect(() => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.expect.md index b28692c67b307..8abc7d6bbdf43 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.expect.md @@ -32,7 +32,16 @@ Found 1 error: Error: You might not need an effect. Derive values in render, not effects. -Derived values (From props: [props]) should be computed during render, rather than in effects. Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user. +Using an effect triggers an additional render which can hurt performance and user experience, potentially briefly showing stale values to the user + +This setState call is setting a derived value that depends on the following reactive sources: + +Props: [props] + +Data Flow Tree: +└── props (Prop) + +See: https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state. error.invalid-derived-state-from-destructured-props.ts:10:4 8 |