mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge f9d13007bf into sapling-pr-archive-jorge-cab
This commit is contained in:
+131
-14
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {CompilerError, Effect} from '..';
|
||||
import {CompilerDiagnostic, CompilerError, Effect} from '..';
|
||||
import {ErrorCategory} from '../CompilerError';
|
||||
import {
|
||||
BlockId,
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
CallExpression,
|
||||
Instruction,
|
||||
isUseStateType,
|
||||
isUseRefType,
|
||||
GeneratedSource,
|
||||
SourceLocation,
|
||||
} from '../HIR';
|
||||
import {eachInstructionLValue, eachInstructionOperand} from '../HIR/visitors';
|
||||
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
@@ -58,6 +61,10 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
const functions: Map<IdentifierId, FunctionExpression> = new Map();
|
||||
|
||||
const derivationCache: Map<IdentifierId, DerivationMetadata> = new Map();
|
||||
const setStateCache: Map<string | undefined | null, Array<Place>> = new Map();
|
||||
|
||||
const effects: Array<HIRFunction> = [];
|
||||
|
||||
if (fn.fnType === 'Hook') {
|
||||
for (const param of fn.params) {
|
||||
if (param.kind === 'Identifier') {
|
||||
@@ -126,11 +133,7 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
) {
|
||||
const effectFunction = functions.get(value.args[0].identifier.id);
|
||||
if (effectFunction != null) {
|
||||
validateEffect(
|
||||
effectFunction.loweredFunc.func,
|
||||
errors,
|
||||
derivationCache,
|
||||
);
|
||||
effects.push(effectFunction.loweredFunc.func);
|
||||
}
|
||||
} else if (isUseStateType(lvalue.identifier)) {
|
||||
const stateValueSource = value.args[0];
|
||||
@@ -142,6 +145,25 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
}
|
||||
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
// Record setState usages everywhere
|
||||
switch (instr.value.kind) {
|
||||
case 'JsxExpression':
|
||||
case 'CallExpression':
|
||||
case 'MethodCall':
|
||||
if (
|
||||
isSetStateType(operand.identifier) &&
|
||||
operand.loc !== GeneratedSource
|
||||
) {
|
||||
if (setStateCache.has(operand.loc.identifierName)) {
|
||||
setStateCache.get(operand.loc.identifierName)!.push(operand);
|
||||
} else {
|
||||
setStateCache.set(operand.loc.identifierName, [operand]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
const operandMetadata = derivationCache.get(operand.identifier.id);
|
||||
|
||||
if (operandMetadata === undefined) {
|
||||
@@ -210,6 +232,10 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
for (const effect of effects) {
|
||||
validateEffect(effect, errors, derivationCache, setStateCache);
|
||||
}
|
||||
|
||||
if (errors.hasAnyErrors()) {
|
||||
throw errors;
|
||||
}
|
||||
@@ -267,14 +293,22 @@ function validateEffect(
|
||||
effectFunction: HIRFunction,
|
||||
errors: CompilerError,
|
||||
derivationCache: Map<IdentifierId, DerivationMetadata>,
|
||||
setStateCache: Map<string | undefined | null, Array<Place>>,
|
||||
): void {
|
||||
const effectSetStateCache: Map<
|
||||
string | undefined | null,
|
||||
Array<Place>
|
||||
> = new Map();
|
||||
const seenBlocks: Set<BlockId> = new Set();
|
||||
|
||||
const effectDerivedSetStateCalls: Array<{
|
||||
value: CallExpression;
|
||||
loc: SourceLocation;
|
||||
sourceIds: Set<IdentifierId>;
|
||||
typeOfValue: TypeOfValue;
|
||||
}> = [];
|
||||
|
||||
const globals: Set<IdentifierId> = new Set();
|
||||
for (const block of effectFunction.body.blocks.values()) {
|
||||
for (const pred of block.preds) {
|
||||
if (!seenBlocks.has(pred)) {
|
||||
@@ -284,6 +318,33 @@ function validateEffect(
|
||||
}
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
// Early return if any instruction is deriving a value from a ref
|
||||
if (isUseRefType(instr.lvalue.identifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
switch (instr.value.kind) {
|
||||
case 'JsxExpression':
|
||||
case 'CallExpression':
|
||||
case 'MethodCall':
|
||||
if (
|
||||
isSetStateType(operand.identifier) &&
|
||||
operand.loc !== GeneratedSource
|
||||
) {
|
||||
if (effectSetStateCache.has(operand.loc.identifierName)) {
|
||||
effectSetStateCache
|
||||
.get(operand.loc.identifierName)!
|
||||
.push(operand);
|
||||
} else {
|
||||
effectSetStateCache.set(operand.loc.identifierName, [operand]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
instr.value.kind === 'CallExpression' &&
|
||||
isSetStateType(instr.value.callee.identifier) &&
|
||||
@@ -297,22 +358,78 @@ function validateEffect(
|
||||
if (argMetadata !== undefined) {
|
||||
effectDerivedSetStateCalls.push({
|
||||
value: instr.value,
|
||||
loc: instr.value.callee.loc,
|
||||
sourceIds: argMetadata.sourcesIds,
|
||||
typeOfValue: argMetadata.typeOfValue,
|
||||
});
|
||||
}
|
||||
} else if (instr.value.kind === 'CallExpression') {
|
||||
const calleeMetadata = derivationCache.get(
|
||||
instr.value.callee.identifier.id,
|
||||
);
|
||||
|
||||
if (
|
||||
calleeMetadata !== undefined &&
|
||||
(calleeMetadata.typeOfValue === 'fromProps' ||
|
||||
calleeMetadata.typeOfValue === 'fromPropsAndState')
|
||||
) {
|
||||
// If the callee is a prop we can't confidently say that it should be derived in render
|
||||
return;
|
||||
}
|
||||
|
||||
if (globals.has(instr.value.callee.identifier.id)) {
|
||||
// If the callee is a global we can't confidently say that it should be derived in render
|
||||
return;
|
||||
}
|
||||
} else if (instr.value.kind === 'LoadGlobal') {
|
||||
globals.add(instr.lvalue.identifier.id);
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
globals.add(operand.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
seenBlocks.add(block.id);
|
||||
}
|
||||
|
||||
for (const derivedSetStateCall of effectDerivedSetStateCalls) {
|
||||
errors.push({
|
||||
category: ErrorCategory.EffectDerivationsOfState,
|
||||
reason:
|
||||
'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)',
|
||||
description: null,
|
||||
loc: derivedSetStateCall.value.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
if (
|
||||
derivedSetStateCall.loc !== GeneratedSource &&
|
||||
effectSetStateCache.has(derivedSetStateCall.loc.identifierName) &&
|
||||
setStateCache.has(derivedSetStateCall.loc.identifierName) &&
|
||||
effectSetStateCache.get(derivedSetStateCall.loc.identifierName)!
|
||||
.length ===
|
||||
setStateCache.get(derivedSetStateCall.loc.identifierName)!.length
|
||||
) {
|
||||
const derivedDepsStr = Array.from(derivedSetStateCall.sourceIds)
|
||||
.map(sourceId => {
|
||||
const sourceMetadata = derivationCache.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}]`;
|
||||
}
|
||||
|
||||
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`,
|
||||
category: ErrorCategory.EffectDerivationsOfState,
|
||||
reason:
|
||||
'You might not need an effect. Derive values in render, not effects.',
|
||||
}).withDetails({
|
||||
kind: 'error',
|
||||
loc: derivedSetStateCall.value.loc,
|
||||
message: 'This should be computed during render, not in an effect',
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({initialName}) {
|
||||
const [name, setName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setName(initialName);
|
||||
}, [initialName]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
// 🟡 If the is also called outside of the effect, it's still wrong but
|
||||
// should be solved by hoisting state
|
||||
<input value={name} onChange={e => setName(e.target.value)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 = (
|
||||
<div>
|
||||
// 🟡 If the is also called outside of the effect, it's still wrong but
|
||||
// should be solved by hoisting state
|
||||
<input value={name} onChange={t3} />
|
||||
</div>
|
||||
);
|
||||
$[4] = name;
|
||||
$[5] = t4;
|
||||
} else {
|
||||
t4 = $[5];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ initialName: "John" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>// 🟡 If the is also called outside of the effect, it's still wrong but // should be solved by hoisting state<input value="John"></div>
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function MockComponent({onSet}) {
|
||||
return <div onClick={() => onSet('clicked')}>Mock Component</div>;
|
||||
}
|
||||
|
||||
function Component({propValue}) {
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
}, [propValue]);
|
||||
|
||||
return <MockComponent onSet={setValue} />;
|
||||
}
|
||||
|
||||
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 MockComponent(t0) {
|
||||
const $ = _c(2);
|
||||
const { onSet } = t0;
|
||||
let t1;
|
||||
if ($[0] !== onSet) {
|
||||
t1 = <div onClick={() => onSet("clicked")}>Mock Component</div>;
|
||||
$[0] = onSet;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
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 = <MockComponent onSet={setValue} />;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propValue: "test" }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>Mock Component</div>
|
||||
+73
@@ -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
|
||||
+75
@@ -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 <div>{value}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{propValue: 'test', onChange: () => {}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 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 = <div>{value}</div>;
|
||||
$[5] = value;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ propValue: "test", onChange: () => {} }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>test</div>
|
||||
+70
@@ -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 <div>{value}</div>;
|
||||
}
|
||||
|
||||
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 = <div>{value}</div>;
|
||||
$[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
|
||||
+4
-2
@@ -32,13 +32,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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);
|
||||
| ^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
10 | } else {
|
||||
11 | setLocalValue('disabled');
|
||||
12 | }
|
||||
|
||||
+4
-2
@@ -28,13 +28,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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:8:4
|
||||
6 |
|
||||
7 | useEffect(() => {
|
||||
> 8 | setCurrInput(input + localConst);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
9 | }, [input, localConst]);
|
||||
10 |
|
||||
11 | return <div>{currInput}</div>;
|
||||
|
||||
+4
-2
@@ -36,13 +36,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
12 | }, [firstName, middleName, lastName]);
|
||||
13 |
|
||||
14 | return (
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({initialName}) {
|
||||
const [name, setName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setName(initialName);
|
||||
}, [initialName]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
// 🟡 If the is also called outside of the effect, it's still wrong but
|
||||
// should be solved by hoisting state
|
||||
<input value={name} onChange={e => setName(e.target.value)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{initialName: 'John'}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
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.derived-state-from-prop-setter-call-outside-effect-no-error.ts:8:4
|
||||
6 |
|
||||
7 | useEffect(() => {
|
||||
> 8 | setName(initialName);
|
||||
| ^^^^^^^^^^^^^^^^^^^^ 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 | }, [initialName]);
|
||||
10 |
|
||||
11 | return (
|
||||
```
|
||||
|
||||
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function MockComponent({onSet}) {
|
||||
return <div onClick={() => onSet('clicked')}>Mock Component</div>;
|
||||
}
|
||||
|
||||
function Component({propValue}) {
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
}, [propValue]);
|
||||
|
||||
return <MockComponent onSet={setValue} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{propValue: 'test'}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
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.derived-state-from-prop-setter-used-outside-effect-no-error.ts:11:4
|
||||
9 | const [value, setValue] = useState(null);
|
||||
10 | useEffect(() => {
|
||||
> 11 | setValue(propValue);
|
||||
| ^^^^^^^^^^^^^^^^^^^ 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)
|
||||
12 | }, [propValue]);
|
||||
13 |
|
||||
14 | return <MockComponent onSet={setValue} />;
|
||||
```
|
||||
|
||||
|
||||
+4
-2
@@ -28,13 +28,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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);
|
||||
| ^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
8 | document.title = `Value: ${value}`;
|
||||
9 | }, [value]);
|
||||
10 |
|
||||
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
|
||||
## 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'}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
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.derived-state-from-ref-and-state-no-error.ts:10:4
|
||||
8 |
|
||||
9 | useEffect(() => {
|
||||
> 10 | setLocal(myRef.current + test);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
11 | }, [test]);
|
||||
12 |
|
||||
13 | return <>{local}</>;
|
||||
```
|
||||
|
||||
|
||||
+4
-2
@@ -33,13 +33,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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);
|
||||
| ^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
13 | localFunction();
|
||||
14 | }, [propValue]);
|
||||
15 |
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue, onChange}) {
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
onChange();
|
||||
}, [propValue]);
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{propValue: 'test', onChange: () => {}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
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.effect-contains-prop-function-call-no-error.ts:7:4
|
||||
5 | const [value, setValue] = useState(null);
|
||||
6 | useEffect(() => {
|
||||
> 7 | setValue(propValue);
|
||||
| ^^^^^^^^^^^^^^^^^^^ 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)
|
||||
8 | onChange();
|
||||
9 | }, [propValue]);
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
function Component({propValue}) {
|
||||
const [value, setValue] = useState(null);
|
||||
useEffect(() => {
|
||||
setValue(propValue);
|
||||
globalCall();
|
||||
}, [propValue]);
|
||||
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{propValue: 'test'}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
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.effect-with-global-function-call-no-error.ts:7:4
|
||||
5 | const [value, setValue] = useState(null);
|
||||
6 | useEffect(() => {
|
||||
> 7 | setValue(propValue);
|
||||
| ^^^^^^^^^^^^^^^^^^^ 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)
|
||||
8 | globalCall();
|
||||
9 | }, [propValue]);
|
||||
10 |
|
||||
```
|
||||
|
||||
|
||||
+4
-2
@@ -29,13 +29,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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(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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
10 | }, [firstName, lastName]);
|
||||
11 |
|
||||
12 | return <div>{fullName}</div>;
|
||||
|
||||
+4
-2
@@ -29,13 +29,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
10 | }, [props.prefix, props.value, props.suffix]);
|
||||
11 |
|
||||
12 | return <div>{displayValue}</div>;
|
||||
|
||||
+4
-2
@@ -30,13 +30,15 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
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 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);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This should be computed during render, not in an effect
|
||||
11 | }, [props.firstName, props.lastName]);
|
||||
12 |
|
||||
13 | return <div>{fullName}</div>;
|
||||
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState, useRef} from 'react';
|
||||
|
||||
export default function Component({test}) {
|
||||
const [local, setLocal] = useState(0);
|
||||
|
||||
const myRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (myRef.current) {
|
||||
setLocal(test);
|
||||
} else {
|
||||
setLocal(test + test);
|
||||
}
|
||||
}, [test]);
|
||||
|
||||
return <>{local}</>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{test: 4}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 2 errors:
|
||||
|
||||
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.ref-conditional-in-effect-no-error.ts:11:6
|
||||
9 | useEffect(() => {
|
||||
10 | if (myRef.current) {
|
||||
> 11 | setLocal(test);
|
||||
| ^^^^^^^^^^^^^^ 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)
|
||||
12 | } else {
|
||||
13 | setLocal(test + test);
|
||||
14 | }
|
||||
|
||||
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.ref-conditional-in-effect-no-error.ts:13:6
|
||||
11 | setLocal(test);
|
||||
12 | } else {
|
||||
> 13 | setLocal(test + test);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ 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)
|
||||
14 | }
|
||||
15 | }, [test]);
|
||||
16 |
|
||||
```
|
||||
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
import {useEffect, useState, useRef} from 'react';
|
||||
|
||||
export default function Component({test}) {
|
||||
const [local, setLocal] = useState(0);
|
||||
|
||||
const myRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (myRef.current) {
|
||||
setLocal(test);
|
||||
} else {
|
||||
setLocal(test + test);
|
||||
}
|
||||
}, [test]);
|
||||
|
||||
return <>{local}</>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{test: 4}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 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(0);
|
||||
|
||||
const myRef = useRef(null);
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== test) {
|
||||
t1 = () => {
|
||||
if (myRef.current) {
|
||||
setLocal(test);
|
||||
} else {
|
||||
setLocal(test + 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: 4 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) 8
|
||||
Reference in New Issue
Block a user