diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.expect.md
new file mode 100644
index 0000000000..c52ede3198
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.expect.md
@@ -0,0 +1,88 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({initialName}) {
+ const [name, setName] = useState('');
+
+ useEffect(() => {
+ setName(initialName);
+ }, [initialName]);
+
+ return (
+
+ // 🟡 If the is also called outside of the effect, it's still wrong but
+ should be solved by hoisting state
+ setName(e.target.value)} />
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{initialName: 'John'}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects
+import { useEffect, useState } from "react";
+
+function Component(t0) {
+ const $ = _c(6);
+ const { initialName } = t0;
+ const [name, setName] = useState("");
+ let t1;
+ let t2;
+ if ($[0] !== initialName) {
+ t1 = () => {
+ setName(initialName);
+ };
+ t2 = [initialName];
+ $[0] = initialName;
+ $[1] = t1;
+ $[2] = t2;
+ } else {
+ t1 = $[1];
+ t2 = $[2];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
+ t3 = (e) => setName(e.target.value);
+ $[3] = t3;
+ } else {
+ t3 = $[3];
+ }
+ let t4;
+ if ($[4] !== name) {
+ t4 = (
+
+ // 🟡 If the is also called outside of the effect, it's still wrong but
+ should be solved by hoisting state
+
+
+ );
+ $[4] = name;
+ $[5] = t4;
+ } else {
+ t4 = $[5];
+ }
+ return t4;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ initialName: "John" }],
+};
+
+```
+
+### Eval output
+(kind: ok) // 🟡 If the is also called outside of the effect, it's still wrong but should be solved by hoisting state
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.js
new file mode 100644
index 0000000000..fe06b84de2
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-call-outside-effect-no-error.js
@@ -0,0 +1,23 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({initialName}) {
+ const [name, setName] = useState('');
+
+ useEffect(() => {
+ setName(initialName);
+ }, [initialName]);
+
+ return (
+
+ // 🟡 If the is also called outside of the effect, it's still wrong but
+ should be solved by hoisting state
+ setName(e.target.value)} />
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{initialName: 'John'}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.expect.md
new file mode 100644
index 0000000000..c6f2a170e1
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.expect.md
@@ -0,0 +1,67 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue}) {
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ setValue(propValue);
+ }, [propValue]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects
+import { useEffect, useState } from "react";
+
+function Component(t0) {
+ const $ = _c(4);
+ const { propValue } = t0;
+ const [, setValue] = useState(null);
+ let t1;
+ let t2;
+ if ($[0] !== propValue) {
+ t1 = () => {
+ setValue(propValue);
+ };
+ t2 = [propValue];
+ $[0] = propValue;
+ $[1] = t1;
+ $[2] = t2;
+ } else {
+ t1 = $[1];
+ t2 = $[2];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
+ t3 = ;
+ $[3] = t3;
+ } else {
+ t3 = $[3];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ propValue: "test" }],
+};
+
+```
+
+### Eval output
+(kind: exception) MockComponent is not defined
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.js
new file mode 100644
index 0000000000..c1979819ad
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-prop-setter-used-outside-effect-no-error.js
@@ -0,0 +1,16 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue}) {
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ setValue(propValue);
+ }, [propValue]);
+
+ return ;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-ref-and-state-no-error.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-ref-and-state-no-error.expect.md
new file mode 100644
index 0000000000..1bb5e18626
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-ref-and-state-no-error.expect.md
@@ -0,0 +1,73 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState, useRef} from 'react';
+
+export default function Component({test}) {
+ const [local, setLocal] = useState('');
+
+ const myRef = useRef(null);
+
+ useEffect(() => {
+ setLocal(myRef.current + test);
+ }, [test]);
+
+ return <>{local}>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{test: 'testString'}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects
+import { useEffect, useState, useRef } from "react";
+
+export default function Component(t0) {
+ const $ = _c(5);
+ const { test } = t0;
+ const [local, setLocal] = useState("");
+
+ const myRef = useRef(null);
+ let t1;
+ let t2;
+ if ($[0] !== test) {
+ t1 = () => {
+ setLocal(myRef.current + test);
+ };
+ t2 = [test];
+ $[0] = test;
+ $[1] = t1;
+ $[2] = t2;
+ } else {
+ t1 = $[1];
+ t2 = $[2];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[3] !== local) {
+ t3 = <>{local}>;
+ $[3] = local;
+ $[4] = t3;
+ } else {
+ t3 = $[4];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ test: "testString" }],
+};
+
+```
+
+### Eval output
+(kind: ok) nulltestString
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-ref-and-state-no-error.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-ref-and-state-no-error.js
new file mode 100644
index 0000000000..dec6ae6daa
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/derived-state-from-ref-and-state-no-error.js
@@ -0,0 +1,19 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState, useRef} from 'react';
+
+export default function Component({test}) {
+ const [local, setLocal] = useState('');
+
+ const myRef = useRef(null);
+
+ useEffect(() => {
+ setLocal(myRef.current + test);
+ }, [test]);
+
+ return <>{local}>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{test: 'testString'}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-prop-function-call-no-error.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-prop-function-call-no-error.expect.md
new file mode 100644
index 0000000000..f861d60807
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-prop-function-call-no-error.expect.md
@@ -0,0 +1,75 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue, onChange}) {
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ setValue(propValue);
+ onChange();
+ }, [propValue]);
+
+ return {value}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects
+import { useEffect, useState } from "react";
+
+function Component(t0) {
+ const $ = _c(7);
+ const { propValue, onChange } = t0;
+ const [value, setValue] = useState(null);
+ let t1;
+ if ($[0] !== onChange || $[1] !== propValue) {
+ t1 = () => {
+ setValue(propValue);
+ onChange();
+ };
+ $[0] = onChange;
+ $[1] = propValue;
+ $[2] = t1;
+ } else {
+ t1 = $[2];
+ }
+ let t2;
+ if ($[3] !== propValue) {
+ t2 = [propValue];
+ $[3] = propValue;
+ $[4] = t2;
+ } else {
+ t2 = $[4];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[5] !== value) {
+ t3 = {value}
;
+ $[5] = value;
+ $[6] = t3;
+ } else {
+ t3 = $[6];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ propValue: "test" }],
+};
+
+```
+
+### Eval output
+(kind: exception) onChange is not a function
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-prop-function-call-no-error.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-prop-function-call-no-error.js
new file mode 100644
index 0000000000..5192feecb1
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-contains-prop-function-call-no-error.js
@@ -0,0 +1,17 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue, onChange}) {
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ setValue(propValue);
+ onChange();
+ }, [propValue]);
+
+ return {value}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-with-global-function-call-no-error.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-with-global-function-call-no-error.expect.md
new file mode 100644
index 0000000000..6d3de1cf6f
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-with-global-function-call-no-error.expect.md
@@ -0,0 +1,70 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue}) {
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ setValue(propValue);
+ globalCall();
+ }, [propValue]);
+
+ return {value}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects
+import { useEffect, useState } from "react";
+
+function Component(t0) {
+ const $ = _c(5);
+ const { propValue } = t0;
+ const [value, setValue] = useState(null);
+ let t1;
+ let t2;
+ if ($[0] !== propValue) {
+ t1 = () => {
+ setValue(propValue);
+ globalCall();
+ };
+ t2 = [propValue];
+ $[0] = propValue;
+ $[1] = t1;
+ $[2] = t2;
+ } else {
+ t1 = $[1];
+ t2 = $[2];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[3] !== value) {
+ t3 = {value}
;
+ $[3] = value;
+ $[4] = t3;
+ } else {
+ t3 = $[4];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ propValue: "test" }],
+};
+
+```
+
+### Eval output
+(kind: exception) globalCall is not defined
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-with-global-function-call-no-error.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-with-global-function-call-no-error.js
new file mode 100644
index 0000000000..6c10927cc1
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/effect-with-global-function-call-no-error.js
@@ -0,0 +1,17 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue}) {
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ setValue(propValue);
+ globalCall();
+ }, [propValue]);
+
+ return {value}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
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
new file mode 100644
index 0000000000..48a9429f19
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.expect.md
@@ -0,0 +1,49 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({value, enabled}) {
+ const [localValue, setLocalValue] = useState('');
+
+ useEffect(() => {
+ if (enabled) {
+ setLocalValue(value);
+ } else {
+ setLocalValue('disabled');
+ }
+ }, [value, enabled]);
+
+ return {localValue}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 'test', enabled: true}],
+};
+
+```
+
+
+## Error
+
+```
+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.
+
+error.derived-state-conditionally-in-effect.ts:9:6
+ 7 | useEffect(() => {
+ 8 | if (enabled) {
+> 9 | setLocalValue(value);
+ | ^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 10 | } else {
+ 11 | setLocalValue('disabled');
+ 12 | }
+```
+
+
\ 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-conditionally-in-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.js
new file mode 100644
index 0000000000..79d83b8925
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-conditionally-in-effect.js
@@ -0,0 +1,21 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({value, enabled}) {
+ const [localValue, setLocalValue] = useState('');
+
+ useEffect(() => {
+ if (enabled) {
+ setLocalValue(value);
+ } else {
+ setLocalValue('disabled');
+ }
+ }, [value, enabled]);
+
+ return {localValue}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 'test', enabled: true}],
+};
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
new file mode 100644
index 0000000000..bd83f3d57a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.expect.md
@@ -0,0 +1,39 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+
+export default function Component(input = 'empty') {
+ const [currInput, setCurrInput] = useState(input);
+
+ useEffect(() => {
+ setCurrInput(input);
+ }, [input]);
+
+ return {currInput}
;
+}
+
+```
+
+
+## Error
+
+```
+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.
+
+error.derived-state-from-default-props.ts:7:4
+ 5 |
+ 6 | useEffect(() => {
+> 7 | setCurrInput(input);
+ | ^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 8 | }, [input]);
+ 9 |
+ 10 | return {currInput}
;
+```
+
+
\ 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-default-props.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.js
new file mode 100644
index 0000000000..c1ea591316
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-default-props.js
@@ -0,0 +1,11 @@
+// @validateNoDerivedComputationsInEffects
+
+export default function Component(input = 'empty') {
+ const [currInput, setCurrInput] = useState(input);
+
+ useEffect(() => {
+ setCurrInput(input);
+ }, [input]);
+
+ return {currInput}
;
+}
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
new file mode 100644
index 0000000000..cd7158584a
--- /dev/null
+++ 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
@@ -0,0 +1,53 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({firstName}) {
+ const [lastName, setLastName] = useState(null);
+ const [fullName, setFullName] = useState(null);
+
+ const middleName = 'D.';
+
+ useEffect(() => {
+ setFullName(firstName + ' ' + middleName + ' ' + lastName);
+ }, [firstName, middleName, lastName]);
+
+ return (
+
+
setLastName(e.target.value)} />
+
{fullName}
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{firstName: 'John'}],
+};
+
+```
+
+
+## Error
+
+```
+Found 1 error:
+
+Error: You might not need an effect. Derive values in render, not effects.
+
+Derived values (From props and 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.
+
+error.derived-state-from-prop-local-state-and-component-scope.ts:11:4
+ 9 |
+ 10 | useEffect(() => {
+> 11 | setFullName(firstName + ' ' + middleName + ' ' + lastName);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 12 | }, [firstName, middleName, lastName]);
+ 13 |
+ 14 | return (
+```
+
+
\ 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-local-state-and-component-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-local-state-and-component-scope.js
new file mode 100644
index 0000000000..4b725d5375
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-local-state-and-component-scope.js
@@ -0,0 +1,25 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({firstName}) {
+ const [lastName, setLastName] = useState(null);
+ const [fullName, setFullName] = useState(null);
+
+ const middleName = 'D.';
+
+ useEffect(() => {
+ setFullName(firstName + ' ' + middleName + ' ' + lastName);
+ }, [firstName, middleName, lastName]);
+
+ return (
+
+
setLastName(e.target.value)} />
+
{fullName}
+
+ );
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{firstName: 'John'}],
+};
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
new file mode 100644
index 0000000000..4988bb2630
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.expect.md
@@ -0,0 +1,45 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+
+function Component({value}) {
+ const [localValue, setLocalValue] = useState('');
+
+ useEffect(() => {
+ setLocalValue(value);
+ document.title = `Value: ${value}`;
+ }, [value]);
+
+ return {localValue}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 'test'}],
+};
+
+```
+
+
+## Error
+
+```
+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.
+
+error.derived-state-from-prop-with-side-effect.ts:7:4
+ 5 |
+ 6 | useEffect(() => {
+> 7 | setLocalValue(value);
+ | ^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 8 | document.title = `Value: ${value}`;
+ 9 | }, [value]);
+ 10 |
+```
+
+
\ 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-with-side-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.js
new file mode 100644
index 0000000000..11c4340eab
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.derived-state-from-prop-with-side-effect.js
@@ -0,0 +1,17 @@
+// @validateNoDerivedComputationsInEffects
+
+function Component({value}) {
+ const [localValue, setLocalValue] = useState('');
+
+ useEffect(() => {
+ setLocalValue(value);
+ document.title = `Value: ${value}`;
+ }, [value]);
+
+ return {localValue}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{value: 'test'}],
+};
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
new file mode 100644
index 0000000000..c2c81bd8ac
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.expect.md
@@ -0,0 +1,50 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue}) {
+ const [value, setValue] = useState(null);
+
+ function localFunction() {
+ console.log('local function');
+ }
+
+ useEffect(() => {
+ setValue(propValue);
+ localFunction();
+ }, [propValue]);
+
+ return {value}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
+
+```
+
+
+## Error
+
+```
+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.
+
+error.effect-contains-local-function-call.ts:12:4
+ 10 |
+ 11 | useEffect(() => {
+> 12 | setValue(propValue);
+ | ^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 13 | localFunction();
+ 14 | }, [propValue]);
+ 15 |
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.js
new file mode 100644
index 0000000000..00e658d896
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.effect-contains-local-function-call.js
@@ -0,0 +1,22 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+function Component({propValue}) {
+ const [value, setValue] = useState(null);
+
+ function localFunction() {
+ console.log('local function');
+ }
+
+ useEffect(() => {
+ setValue(propValue);
+ localFunction();
+ }, [propValue]);
+
+ return {value}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{propValue: 'test'}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/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
similarity index 55%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-derived-computation-in-effect.expect.md
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.expect.md
index d97a665ae6..71bd414864 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/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
@@ -10,7 +10,7 @@ function BadExample() {
// 🔴 Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
- setFullName(capitalize(firstName + ' ' + lastName));
+ setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return {fullName}
;
@@ -24,13 +24,15 @@ function BadExample() {
```
Found 1 error:
-Error: Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)
+Error: You might not need an effect. Derive values in render, not effects.
+
+Derived values (From local state: []) 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.
error.invalid-derived-computation-in-effect.ts:9:4
7 | const [fullName, setFullName] = useState('');
8 | useEffect(() => {
-> 9 | setFullName(capitalize(firstName + ' ' + lastName));
- | ^^^^^^^^^^^ Values derived from props and state should be calculated during render, not in an effect. (https://react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)
+> 9 | setFullName(firstName + ' ' + lastName);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
10 | }, [firstName, lastName]);
11 |
12 | return {fullName}
;
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-derived-computation-in-effect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.js
similarity index 86%
rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-derived-computation-in-effect.js
rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.js
index d803d3c4a3..63d18c9c17 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-derived-computation-in-effect.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-computation-in-effect.js
@@ -6,7 +6,7 @@ function BadExample() {
// 🔴 Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
- setFullName(capitalize(firstName + ' ' + lastName));
+ setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return {fullName}
;
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
new file mode 100644
index 0000000000..6bf62694c3
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.expect.md
@@ -0,0 +1,46 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+export default function Component(props) {
+ const [displayValue, setDisplayValue] = useState('');
+
+ useEffect(() => {
+ const computed = props.prefix + props.value + props.suffix;
+ setDisplayValue(computed);
+ }, [props.prefix, props.value, props.suffix]);
+
+ return {displayValue}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{prefix: '[', value: 'test', suffix: ']'}],
+};
+
+```
+
+
+## Error
+
+```
+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.
+
+error.invalid-derived-state-from-computed-props.ts:9:4
+ 7 | useEffect(() => {
+ 8 | const computed = props.prefix + props.value + props.suffix;
+> 9 | setDisplayValue(computed);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 10 | }, [props.prefix, props.value, props.suffix]);
+ 11 |
+ 12 | return {displayValue}
;
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.js
new file mode 100644
index 0000000000..31fb30cef9
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-computed-props.js
@@ -0,0 +1,18 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+export default function Component(props) {
+ const [displayValue, setDisplayValue] = useState('');
+
+ useEffect(() => {
+ const computed = props.prefix + props.value + props.suffix;
+ setDisplayValue(computed);
+ }, [props.prefix, props.value, props.suffix]);
+
+ return {displayValue}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{prefix: '[', value: 'test', suffix: ']'}],
+};
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
new file mode 100644
index 0000000000..c1c78ce47d
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.expect.md
@@ -0,0 +1,47 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+export default function Component({props}) {
+ const [fullName, setFullName] = useState(
+ props.firstName + ' ' + props.lastName
+ );
+
+ useEffect(() => {
+ setFullName(props.firstName + ' ' + props.lastName);
+ }, [props.firstName, props.lastName]);
+
+ return {fullName}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{props: {firstName: 'John', lastName: 'Doe'}}],
+};
+
+```
+
+
+## Error
+
+```
+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.
+
+error.invalid-derived-state-from-destructured-props.ts:10:4
+ 8 |
+ 9 | useEffect(() => {
+> 10 | setFullName(props.firstName + ' ' + props.lastName);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
+ 11 | }, [props.firstName, props.lastName]);
+ 12 |
+ 13 | return {fullName}
;
+```
+
+
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.js
new file mode 100644
index 0000000000..2df3df1d52
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/error.invalid-derived-state-from-destructured-props.js
@@ -0,0 +1,19 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState} from 'react';
+
+export default function Component({props}) {
+ const [fullName, setFullName] = useState(
+ props.firstName + ' ' + props.lastName
+ );
+
+ useEffect(() => {
+ setFullName(props.firstName + ' ' + props.lastName);
+ }, [props.firstName, props.lastName]);
+
+ return {fullName}
;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{props: {firstName: 'John', lastName: 'Doe'}}],
+};
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/ref-conditional-in-effect-no-error.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/ref-conditional-in-effect-no-error.expect.md
new file mode 100644
index 0000000000..85050d2459
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/ref-conditional-in-effect-no-error.expect.md
@@ -0,0 +1,82 @@
+
+## Input
+
+```javascript
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState, useRef} from 'react';
+
+export default function Component({test}) {
+ const [local, setLocal] = useState('');
+
+ const myRef = useRef(null);
+
+ useEffect(() => {
+ if (myRef.current) {
+ setLocal(test + 'Available');
+ } else {
+ setLocal(test + 'NotAvailable');
+ }
+ }, [test]);
+
+ return <>{local}>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{test: 'testString'}],
+};
+
+```
+
+## Code
+
+```javascript
+import { c as _c } from "react/compiler-runtime"; // @validateNoDerivedComputationsInEffects
+import { useEffect, useState, useRef } from "react";
+
+export default function Component(t0) {
+ const $ = _c(5);
+ const { test } = t0;
+ const [local, setLocal] = useState("");
+
+ const myRef = useRef(null);
+ let t1;
+ let t2;
+ if ($[0] !== test) {
+ t1 = () => {
+ if (myRef.current) {
+ setLocal(test + "Available");
+ } else {
+ setLocal(test + "NotAvailable");
+ }
+ };
+
+ t2 = [test];
+ $[0] = test;
+ $[1] = t1;
+ $[2] = t2;
+ } else {
+ t1 = $[1];
+ t2 = $[2];
+ }
+ useEffect(t1, t2);
+ let t3;
+ if ($[3] !== local) {
+ t3 = <>{local}>;
+ $[3] = local;
+ $[4] = t3;
+ } else {
+ t3 = $[4];
+ }
+ return t3;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{ test: "testString" }],
+};
+
+```
+
+### Eval output
+(kind: ok) testStringNotAvailable
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/ref-conditional-in-effect-no-error.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/ref-conditional-in-effect-no-error.js
new file mode 100644
index 0000000000..072490988a
--- /dev/null
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/effect-derived-computations/ref-conditional-in-effect-no-error.js
@@ -0,0 +1,23 @@
+// @validateNoDerivedComputationsInEffects
+import {useEffect, useState, useRef} from 'react';
+
+export default function Component({test}) {
+ const [local, setLocal] = useState('');
+
+ const myRef = useRef(null);
+
+ useEffect(() => {
+ if (myRef.current) {
+ setLocal(test + 'Available');
+ } else {
+ setLocal(test + 'NotAvailable');
+ }
+ }, [test]);
+
+ return <>{local}>;
+}
+
+export const FIXTURE_ENTRYPOINT = {
+ fn: Component,
+ params: [{test: 'testString'}],
+};