Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
50850bbac6 [compiler] Upstream experimental flow integration 2025-08-06 13:22:12 -07:00
49 changed files with 269 additions and 1980 deletions

View File

@@ -9,7 +9,6 @@ import {
import * as t from '@babel/types';
import * as TypeErrors from './TypeErrors';
import {assertExhaustive} from '../Utils/utils';
import {FlowType} from './FlowTypes';
export const DEBUG = false;
@@ -197,6 +196,8 @@ export function makeVariableId(id: number): VariableId {
return id as VariableId;
}
import {inspect} from 'util';
import {FlowType} from './FlowTypes';
export function printConcrete<T>(
type: ConcreteType<T>,
printType: (_: T) => string,
@@ -240,7 +241,7 @@ export function printConcrete<T>(
case 'Generic':
return `T${type.id}`;
case 'Object': {
const name = `Object [${[...type.members.keys()].map(key => JSON.stringify(key)).join(', ')}]`;
const name = `Object ${inspect([...type.members.keys()])}`;
return `${name}`;
}
case 'Tuple': {

View File

@@ -5,52 +5,20 @@
* LICENSE file in the root directory of this source tree.
*/
import {
CompilerDiagnostic,
CompilerError,
Effect,
ErrorSeverity,
SourceLocation,
} from '..';
import {CompilerError, ErrorSeverity, SourceLocation} from '..';
import {
ArrayExpression,
BasicBlock,
BlockId,
FunctionExpression,
HIRFunction,
IdentifierId,
Instruction,
Place,
isSetStateType,
isUseEffectHookType,
isUseStateType,
GeneratedSource,
} from '../HIR';
import {eachInstructionOperand, eachInstructionLValue} from '../HIR/visitors';
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
import {assertExhaustive} from '../Utils/utils';
type SetStateCall = {
loc: SourceLocation;
derivedDep: DerivationMetadata;
setStateId: IdentifierId;
};
type TypeOfValue = 'ignored' | 'fromProps' | 'fromState' | 'fromPropsOrState';
type DerivationMetadata = {
typeOfValue: TypeOfValue;
place: Place;
sources: Array<Place>;
};
type ErrorMetadata = {
type: TypeOfValue;
description: string | undefined;
loc: SourceLocation;
setStateName: string | undefined | null;
derivedDepsNames: Array<string>;
};
import {
eachInstructionValueOperand,
eachTerminalOperand,
} from '../HIR/visitors';
/**
* Validates that useEffect is not used for derived computations which could/should
@@ -79,46 +47,12 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
const candidateDependencies: Map<IdentifierId, ArrayExpression> = new Map();
const functions: Map<IdentifierId, FunctionExpression> = new Map();
const locals: Map<IdentifierId, IdentifierId> = new Map();
const derivationCache: Map<IdentifierId, DerivationMetadata> = new Map();
const shadowingUseState: Map<string, Array<SourceLocation>> = new Map();
const effectSetStates: Map<
string | undefined | null,
Array<Place>
> = new Map();
const setStateCalls: Map<string | undefined | null, Array<Place>> = new Map();
const errors: Array<ErrorMetadata> = [];
if (fn.fnType === 'Hook') {
for (const param of fn.params) {
if (param.kind === 'Identifier') {
derivationCache.set(param.identifier.id, {
place: param,
sources: [param],
typeOfValue: 'fromProps',
});
}
}
} else if (fn.fnType === 'Component') {
const props = fn.params[0];
if (props != null && props.kind === 'Identifier') {
derivationCache.set(props.identifier.id, {
place: props,
sources: [props],
typeOfValue: 'fromProps',
});
}
}
const errors = new CompilerError();
for (const block of fn.body.blocks.values()) {
parseBlockPhi(block, derivationCache);
for (const instr of block.instructions) {
const {lvalue, value} = instr;
parseInstr(instr, derivationCache, setStateCalls, shadowingUseState);
if (value.kind === 'LoadLocal') {
locals.set(lvalue.identifier.id, value.place.identifier.id);
} else if (value.kind === 'ArrayExpression') {
@@ -131,7 +65,6 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
) {
const callee =
value.kind === 'CallExpression' ? value.callee : value.property;
if (
isUseEffectHookType(callee.identifier) &&
value.args.length === 2 &&
@@ -156,8 +89,6 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
validateEffect(
effectFunction.loweredFunc.func,
dependencies,
derivationCache,
effectSetStates,
errors,
);
}
@@ -165,325 +96,43 @@ export function validateNoDerivedComputationsInEffects(fn: HIRFunction): void {
}
}
}
const compilerError = generateCompilerError(
setStateCalls,
effectSetStates,
shadowingUseState,
errors,
);
if (compilerError.hasErrors()) {
throw compilerError;
}
}
function generateCompilerError(
setStateCalls: Map<string | undefined | null, Array<Place>>,
effectSetStates: Map<string | undefined | null, Array<Place>>,
shadowingUseState: Map<string, Array<SourceLocation>>,
errors: Array<ErrorMetadata>,
): CompilerError {
const throwableErrors = new CompilerError();
for (const error of errors) {
let compilerDiagnostic: CompilerDiagnostic | undefined = undefined;
/*
* If we use a setState from an invalid useEffect elsewhere then we probably have to
* hoist state up, else we should calculate in render
*/
if (
setStateCalls.get(error.setStateName)?.length !=
effectSetStates.get(error.setStateName)?.length &&
error.type !== 'fromState'
) {
compilerDiagnostic = CompilerDiagnostic.create({
description: `The setState within a useEffect is deriving from ${error.description} Instead of shadowing the prop with local state, hoist the state to the parent component and update it there. If you are purposefully initializing state with a prop, and want to update it when a prop changes, do so conditionally in render`,
category: `You might not need an effect. Local state shadows parent state.`,
severity: ErrorSeverity.InvalidReact,
}).withDetail({
kind: 'error',
loc: error.loc,
message: `this derives values from props ${error.type === 'fromPropsOrState' ? 'and local state ' : ''}to synchronize state`,
});
for (const derivedDep of error.derivedDepsNames) {
if (shadowingUseState.has(derivedDep)) {
for (const loc of shadowingUseState.get(derivedDep)!) {
compilerDiagnostic.withDetail({
kind: 'error',
loc: loc,
message: `this useState shadows ${derivedDep}`,
});
}
}
}
for (const [key, setStateCallArray] of effectSetStates) {
if (setStateCallArray.length === 0) {
continue;
}
const nonUseEffectSetStateCalls = setStateCalls.get(key);
if (nonUseEffectSetStateCalls) {
for (const place of nonUseEffectSetStateCalls) {
if (!setStateCallArray.includes(place)) {
compilerDiagnostic.withDetail({
kind: 'error',
loc: place.loc,
message:
'this setState updates the shadowed state, but should call an onChange event from the parent',
});
}
}
}
}
} else {
compilerDiagnostic = CompilerDiagnostic.create({
description: `${error.description} Derived values 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: `You might not need an effect. Derive values in render, not effects.`,
severity: ErrorSeverity.InvalidReact,
}).withDetail({
kind: 'error',
loc: error.loc,
message: 'This should be computed during render, not in an effect',
});
}
if (compilerDiagnostic) {
throwableErrors.pushDiagnostic(compilerDiagnostic);
}
}
return throwableErrors;
}
function joinValue(
lvalueType: TypeOfValue,
valueType: TypeOfValue,
): TypeOfValue {
if (lvalueType === 'ignored') return valueType;
if (valueType === 'ignored') return lvalueType;
if (lvalueType === valueType) return lvalueType;
return 'fromPropsOrState';
}
function updateDerivationMetadata(
target: Place,
sources: Array<DerivationMetadata> | undefined,
typeOfValue: TypeOfValue | undefined,
derivationCache: Map<IdentifierId, DerivationMetadata>,
): void {
let newValue: DerivationMetadata = {
place: target,
sources: [],
typeOfValue: typeOfValue ?? 'ignored',
};
if (sources !== undefined) {
for (const source of sources) {
/*
* If the identifier of the source is a promoted identifier, then
* we should set the target as the source.
*/
for (const place of source.sources) {
if (
place.identifier.name === null ||
place.identifier.name?.kind === 'promoted'
) {
newValue.sources.push(target);
} else {
newValue.sources.push(place);
}
}
}
}
derivationCache.set(target.identifier.id, newValue);
}
function parseInstr(
instr: Instruction,
derivationCache: Map<IdentifierId, DerivationMetadata>,
setStateCalls: Map<string | undefined | null, Array<Place>>,
shadowingUseState: Map<string, Array<SourceLocation>>,
): void {
// Recursively parse function expressions
if (instr.value.kind === 'FunctionExpression') {
for (const [, block] of instr.value.loweredFunc.func.body.blocks) {
for (const instr of block.instructions) {
parseInstr(instr, derivationCache, setStateCalls, shadowingUseState);
}
}
}
let typeOfValue: TypeOfValue = 'ignored';
let sources: Array<DerivationMetadata> = [];
// Catch setState calls
if (
instr.value.kind === 'CallExpression' &&
isSetStateType(instr.value.callee.identifier) &&
instr.value.args.length === 1 &&
instr.value.args[0].kind === 'Identifier' &&
instr.value.callee.loc !== GeneratedSource
) {
if (setStateCalls.has(instr.value.callee.loc.identifierName)) {
setStateCalls
.get(instr.value.callee.loc.identifierName)!
.push(instr.value.callee);
} else {
setStateCalls.set(instr.value.callee.loc.identifierName, [
instr.value.callee,
]);
}
}
for (const operand of eachInstructionOperand(instr)) {
const opSource = derivationCache.get(operand.identifier.id);
if (opSource === undefined) {
continue;
}
typeOfValue = joinValue(typeOfValue, opSource.typeOfValue);
sources.push(opSource);
if (
instr.value.kind === 'Destructure' &&
instr.value.lvalue.pattern.kind === 'ArrayPattern' &&
isUseStateType(instr.value.value.identifier) &&
opSource.typeOfValue === 'fromProps'
) {
opSource.sources.forEach(source => {
if (instr.value.kind !== 'Destructure') {
return;
}
if (source.identifier.name !== null) {
if (shadowingUseState.has(source.identifier.name.value)) {
shadowingUseState
.get(source.identifier.name.value)
?.push(instr.value.value.loc);
} else {
shadowingUseState.set(source.identifier.name.value, [
instr.value.value.loc,
]);
}
}
});
}
}
// Catch useState hook calls
if (
instr.value.kind === 'Destructure' &&
instr.value.lvalue.pattern.kind === 'ArrayPattern' &&
isUseStateType(instr.value.value.identifier)
) {
const stateValueSource = instr.value.lvalue.pattern.items[0];
if (stateValueSource.kind === 'Identifier') {
sources.push({
place: stateValueSource,
typeOfValue: typeOfValue,
sources: [stateValueSource],
});
}
typeOfValue = joinValue(typeOfValue, 'fromState');
}
if (typeOfValue !== 'ignored') {
for (const lvalue of eachInstructionLValue(instr)) {
updateDerivationMetadata(lvalue, sources, typeOfValue, derivationCache);
}
for (const operand of eachInstructionOperand(instr)) {
switch (operand.effect) {
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.ConditionallyMutateIterator:
case Effect.Mutate: {
if (isMutable(instr, operand)) {
updateDerivationMetadata(
operand,
sources,
typeOfValue,
derivationCache,
);
}
break;
}
case Effect.Freeze:
case Effect.Read: {
// no-op
break;
}
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
description: null,
loc: operand.loc,
suggestions: null,
});
}
default: {
assertExhaustive(
operand.effect,
`Unexpected effect kind \`${operand.effect}\``,
);
}
}
}
}
}
function parseBlockPhi(
block: BasicBlock,
derivationCache: Map<IdentifierId, DerivationMetadata>,
): void {
for (const phi of block.phis) {
for (const operand of phi.operands.values()) {
const phiSource = derivationCache.get(operand.identifier.id);
if (phiSource !== undefined) {
updateDerivationMetadata(
phi.place,
[phiSource],
phiSource?.typeOfValue,
derivationCache,
);
}
}
if (errors.hasErrors()) {
throw errors;
}
}
function validateEffect(
effectFunction: HIRFunction,
effectDeps: Array<IdentifierId>,
derivationCache: Map<IdentifierId, DerivationMetadata>,
effectSetStates: Map<string | undefined | null, Array<Place>>,
errors: Array<ErrorMetadata>,
errors: CompilerError,
): void {
let isUsingDerivedDeps = false;
for (const operand of effectFunction.context) {
if (isSetStateType(operand.identifier)) {
continue;
} else if (effectDeps.find(dep => dep === operand.identifier.id) != null) {
continue;
} else {
// Captured something other than the effect dep or setState
return;
}
}
for (const dep of effectDeps) {
const depMetadata = derivationCache.get(dep);
if (
effectFunction.context.find(operand => operand.identifier.id === dep) !=
null ||
(depMetadata !== undefined && depMetadata.typeOfValue !== 'ignored')
effectFunction.context.find(operand => operand.identifier.id === dep) ==
null
) {
isUsingDerivedDeps = true;
// effect dep wasn't actually used in the function
return;
}
}
if (!isUsingDerivedDeps) {
// no prop/state derived deps were used in the body of the effect
return;
const seenBlocks: Set<BlockId> = new Set();
const values: Map<IdentifierId, Array<IdentifierId>> = new Map();
for (const dep of effectDeps) {
values.set(dep, [dep]);
}
const seenBlocks: Set<BlockId> = new Set();
const derivedSetStateCall: Array<SetStateCall> = [];
const setStateLocations: Array<SourceLocation> = [];
for (const block of effectFunction.body.blocks.values()) {
for (const pred of block.preds) {
if (!seenBlocks.has(pred)) {
@@ -491,29 +140,21 @@ function validateEffect(
return;
}
}
parseBlockPhi(block, derivationCache);
for (const instr of block.instructions) {
if (
instr.value.kind === 'CallExpression' &&
isSetStateType(instr.value.callee.identifier) &&
instr.value.args.length === 1 &&
instr.value.args[0].kind === 'Identifier' &&
instr.value.callee.loc !== GeneratedSource &&
instr.value.callee.loc.identifierName !== undefined &&
instr.value.callee.loc.identifierName !== null
) {
if (effectSetStates.has(instr.value.callee.loc.identifierName)) {
effectSetStates
.get(instr.value.callee.loc.identifierName)!
.push(instr.value.callee);
} else {
effectSetStates.set(instr.value.callee.loc.identifierName, [
instr.value.callee,
]);
for (const phi of block.phis) {
const aggregateDeps: Set<IdentifierId> = new Set();
for (const operand of phi.operands.values()) {
const deps = values.get(operand.identifier.id);
if (deps != null) {
for (const dep of deps) {
aggregateDeps.add(dep);
}
}
}
if (aggregateDeps.size !== 0) {
values.set(phi.place.identifier.id, Array.from(aggregateDeps));
}
}
for (const instr of block.instructions) {
switch (instr.value.kind) {
case 'Primitive':
case 'JSXText':
@@ -521,6 +162,10 @@ function validateEffect(
break;
}
case 'LoadLocal': {
const deps = values.get(instr.value.place.identifier.id);
if (deps != null) {
values.set(instr.lvalue.identifier.id, deps);
}
break;
}
case 'ComputedLoad':
@@ -529,61 +174,57 @@ function validateEffect(
case 'TemplateLiteral':
case 'CallExpression':
case 'MethodCall': {
const aggregateDeps: Set<IdentifierId> = new Set();
for (const operand of eachInstructionValueOperand(instr.value)) {
const deps = values.get(operand.identifier.id);
if (deps != null) {
for (const dep of deps) {
aggregateDeps.add(dep);
}
}
}
if (aggregateDeps.size !== 0) {
values.set(instr.lvalue.identifier.id, Array.from(aggregateDeps));
}
if (
instr.value.kind === 'CallExpression' &&
isSetStateType(instr.value.callee.identifier) &&
instr.value.args.length === 1 &&
instr.value.args[0].kind === 'Identifier'
) {
const derivedDep = derivationCache.get(
instr.value.args[0].identifier.id,
);
if (derivedDep !== undefined) {
derivedSetStateCall.push({
loc: instr.value.callee.loc,
setStateId: instr.value.callee.identifier.id,
derivedDep: derivedDep,
});
const deps = values.get(instr.value.args[0].identifier.id);
if (deps != null && new Set(deps).size === effectDeps.length) {
setStateLocations.push(instr.value.callee.loc);
} else {
// doesn't depend on any deps
return;
}
}
break;
}
default: {
return;
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
if (values.has(operand.identifier.id)) {
//
return;
}
}
seenBlocks.add(block.id);
}
for (const call of derivedSetStateCall) {
const derivedDepsStr = Array.from(call.derivedDep.sources)
.map(place => {
return place.identifier.name?.value;
})
.filter(Boolean)
.join(', ');
let errorDescription = '';
if (call.derivedDep.typeOfValue === 'fromProps') {
errorDescription = `props [${derivedDepsStr}].`;
} else if (call.derivedDep.typeOfValue === 'fromState') {
errorDescription = `local state [${derivedDepsStr}].`;
} else {
errorDescription = `both props and local state [${derivedDepsStr}].`;
}
for (const loc of setStateLocations) {
errors.push({
type: call.derivedDep.typeOfValue,
description: `${errorDescription}`,
loc: call.loc,
setStateName:
call.loc !== GeneratedSource ? call.loc.identifierName : undefined,
derivedDepsNames: Array.from(call.derivedDep.sources)
.map(place => {
return place.identifier.name?.value ?? '';
})
.filter(Boolean),
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,
severity: ErrorSeverity.InvalidReact,
loc,
suggestions: null,
});
}
}

View File

@@ -24,15 +24,13 @@ function BadExample() {
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from local state [firstName, lastName]. Derived values 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: 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.invalid-derived-computation-in-effect.ts:9:4
7 | const [fullName, setFullName] = useState('');
8 | useEffect(() => {
> 9 | setFullName(capitalize(firstName + ' ' + lastName));
| ^^^^^^^^^^^ This should be computed during render, not in an effect
| ^^^^^^^^^^^ 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)
10 | }, [firstName, lastName]);
11 |
12 | return <div>{fullName}</div>;

View File

@@ -1,87 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({initialName}) {
const [name, setName] = useState('');
useEffect(() => {
setName(initialName);
}, []);
return (
<div>
<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;
if ($[0] !== initialName) {
t1 = () => {
setName(initialName);
};
$[0] = initialName;
$[1] = t1;
} else {
t1 = $[1];
}
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[2] = t2;
} else {
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>
<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><input value="John"></div>

View File

@@ -1,21 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({initialName}) {
const [name, setName] = useState('');
useEffect(() => {
setName(initialName);
}, []);
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{initialName: 'John'}],
};

View File

@@ -1,51 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({prefix}) {
const [name, setName] = useState('');
const [displayName, setDisplayName] = useState('');
useEffect(() => {
setDisplayName(prefix + name);
}, [prefix, name]);
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<div>{displayName}</div>
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prefix: 'Hello, '}],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from both props and local state [prefix, name]. Derived values 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.bug-derived-state-from-mixed-deps.ts:9:4
7 |
8 | useEffect(() => {
> 9 | setDisplayName(prefix + name);
| ^^^^^^^^^^^^^^ This should be computed during render, not in an effect
10 | }, [prefix, name]);
11 |
12 | return (
```

View File

@@ -1,23 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({prefix}) {
const [name, setName] = useState('');
const [displayName, setDisplayName] = useState('');
useEffect(() => {
setDisplayName(prefix + name);
}, [prefix, name]);
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<div>{displayName}</div>
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prefix: 'Hello, '}],
};

View File

@@ -1,76 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useState, useEffect} from 'react';
function Component({props, number}) {
const nothing = 0;
const missDirection = number;
const [displayValue, setDisplayValue] = useState(props.prefix + missDirection + nothing);
useEffect(() => {
setDisplayValue(props.prefix + missDirection + nothing);
}, [props.prefix, missDirection, nothing]);
return (
<div
onClick={() => {
setDisplayValue('clicked');
}}>
{displayValue}
</div>
);
}
```
## Error
```
Found 1 error:
Error: Local state shadows parent state.
This setState() appears to derive a value from props [props, number]. This state value shadows a value passed as a prop. Instead of shadowing the prop with local state, hoist the state to the parent component and update it there.
error.derived-state-from-shadowed-props.ts:7:42
5 | const nothing = 0;
6 | const missDirection = number;
> 7 | const [displayValue, setDisplayValue] = useState(props.prefix + missDirection + nothing);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this useState shadows props
8 |
9 | useEffect(() => {
10 | setDisplayValue(props.prefix + missDirection + nothing);
error.derived-state-from-shadowed-props.ts:7:42
5 | const nothing = 0;
6 | const missDirection = number;
> 7 | const [displayValue, setDisplayValue] = useState(props.prefix + missDirection + nothing);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this useState shadows number
8 |
9 | useEffect(() => {
10 | setDisplayValue(props.prefix + missDirection + nothing);
error.derived-state-from-shadowed-props.ts:10:4
8 |
9 | useEffect(() => {
> 10 | setDisplayValue(props.prefix + missDirection + nothing);
| ^^^^^^^^^^^^^^^ this setState synchronizes the state
11 | }, [props.prefix, missDirection, nothing]);
12 |
13 | return (
error.derived-state-from-shadowed-props.ts:16:8
14 | <div
15 | onClick={() => {
> 16 | setDisplayValue('clicked');
| ^^^^^^^^^^^^^^^ this setState updates the shadowed state, but should call an onChange event from the parent
17 | }}>
18 | {displayValue}
19 | </div>
```

View File

@@ -1,21 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useState, useEffect} from 'react';
function Component({props, number}) {
const nothing = 0;
const missDirection = number;
const [displayValue, setDisplayValue] = useState(props.prefix + missDirection + nothing);
useEffect(() => {
setDisplayValue(props.prefix + missDirection + nothing);
}, [props.prefix, missDirection, nothing]);
return (
<div
onClick={() => {
setDisplayValue('clicked');
}}>
{displayValue}
</div>
);
}

View File

@@ -1,49 +0,0 @@
## 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 <div>{localValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 'test', enabled: true}],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from props [value]. Derived values 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-with-conditional.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 | }
```

View File

@@ -1,21 +0,0 @@
// @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 <div>{localValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 'test', enabled: true}],
};

View File

@@ -1,47 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({value}) {
const [localValue, setLocalValue] = useState('');
useEffect(() => {
console.log('Value changed:', value);
setLocalValue(value);
document.title = `Value: ${value}`;
}, [value]);
return <div>{localValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 'test'}],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from props [value]. Derived values 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-with-side-effects.ts:9:4
7 | useEffect(() => {
8 | console.log('Value changed:', value);
> 9 | setLocalValue(value);
| ^^^^^^^^^^^^^ This should be computed during render, not in an effect
10 | document.title = `Value: ${value}`;
11 | }, [value]);
12 |
```

View File

@@ -1,19 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({value}) {
const [localValue, setLocalValue] = useState('');
useEffect(() => {
console.log('Value changed:', value);
setLocalValue(value);
document.title = `Value: ${value}`;
}, [value]);
return <div>{localValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 'test'}],
};

View File

@@ -1,46 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
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 <div>{displayValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prefix: '[', value: 'test', suffix: ']'}],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from props [props, props, props]. Derived values 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-props-computed.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 <div>{displayValue}</div>;
```

View File

@@ -1,18 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
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 <div>{displayValue}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prefix: '[', value: 'test', suffix: ']'}],
};

View File

@@ -1,45 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({props}) {
const [fullName, setFullName] = useState(props.firstName + ' ' + props.lastName);
useEffect(() => {
setFullName(props.firstName + ' ' + props.lastName);
}, [props.firstName, props.lastName]);
return <div>{fullName}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{firstName: 'John', lastName: 'Doe'}],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from props [props, props]. Derived values 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-props-destructured.ts:8:4
6 |
7 | useEffect(() => {
> 8 | setFullName(props.firstName + ' ' + props.lastName);
| ^^^^^^^^^^^ This should be computed during render, not in an effect
9 | }, [props.firstName, props.lastName]);
10 |
11 | return <div>{fullName}</div>;
```

View File

@@ -1,17 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({props}) {
const [fullName, setFullName] = useState(props.firstName + ' ' + props.lastName);
useEffect(() => {
setFullName(props.firstName + ' ' + props.lastName);
}, [props.firstName, props.lastName]);
return <div>{fullName}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{firstName: 'John', lastName: 'Doe'}],
};

View File

@@ -1,45 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({firstName, lastName}) {
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <div>{fullName}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{firstName: 'John', lastName: 'Doe'}],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from props [firstName, lastName]. Derived values 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-props-in-effect.ts:8:4
6 |
7 | useEffect(() => {
> 8 | setFullName(firstName + ' ' + lastName);
| ^^^^^^^^^^^ This should be computed during render, not in an effect
9 | }, [firstName, lastName]);
10 |
11 | return <div>{fullName}</div>;
```

View File

@@ -1,17 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component({firstName, lastName}) {
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return <div>{fullName}</div>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{firstName: 'John', lastName: 'Doe'}],
};

View File

@@ -1,43 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
export default function InProductLobbyGeminiCard(
input = 'empty',
) {
const [currInput, setCurrInput] = useState(input);
useEffect(() => {
setCurrInput(input)
}, [input]);
return (
<div>{currInput}</div>
)
}
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from props [input]. Derived values 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-props-with-default-value.ts:9:4
7 |
8 | useEffect(() => {
> 9 | setCurrInput(input)
| ^^^^^^^^^^^^ This should be computed during render, not in an effect
10 | }, [input]);
11 |
12 | return (
```

View File

@@ -1,15 +0,0 @@
// @validateNoDerivedComputationsInEffects
export default function InProductLobbyGeminiCard(
input = 'empty',
) {
const [currInput, setCurrInput] = useState(input);
useEffect(() => {
setCurrInput(input)
}, [input]);
return (
<div>{currInput}</div>
)
}

View File

@@ -1,53 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component() {
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Doe');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return (
<div>
<input value={firstName} onChange={e => setFirstName(e.target.value)} />
<input value={lastName} onChange={e => setLastName(e.target.value)} />
<div>{fullName}</div>
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};
```
## Error
```
Found 1 error:
Error: Derive values in render, not effects.
This setState() appears to derive a value from local state [firstName, lastName]. Derived values 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-state-in-effect.ts:10:4
8 |
9 | useEffect(() => {
> 10 | setFullName(firstName + ' ' + lastName);
| ^^^^^^^^^^^ This should be computed during render, not in an effect
11 | }, [firstName, lastName]);
12 |
13 | return (
```

View File

@@ -1,25 +0,0 @@
// @validateNoDerivedComputationsInEffects
import {useEffect, useState} from 'react';
function Component() {
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Doe');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
return (
<div>
<input value={firstName} onChange={e => setFirstName(e.target.value)} />
<input value={lastName} onChange={e => setLastName(e.target.value)} />
<div>{fullName}</div>
</div>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [],
};

View File

@@ -1,61 +0,0 @@
## Input
```javascript
// @validateNoDerivedComputationsInEffects
function EndDate({startDate, endDate, onStartDateChange}) {
const [localStartDate, setLocalStartDate] = useState(startDate);
useEffect(() => {
setLocalStartDate(startDate);
}, [startDate]);
const onChange = (date) => {
setLocalStartDate(date);
onStartDateChange(date);
}
return <DateInput value={localStartDate} second={endDate} onChange={onChange} />
}
```
## Error
```
Found 1 error:
Error: Local state shadows parent state.
This setState() appears to derive a value from props [startDate]. This state value shadows a value passed as a prop. Instead of shadowing the prop with local state, hoist the state to the parent component and update it there.
error.shadowed-props-with-onchange.ts:4:47
2 |
3 | function EndDate({startDate, endDate, onStartDateChange}) {
> 4 | const [localStartDate, setLocalStartDate] = useState(startDate);
| ^^^^^^^^^^^^^^^^^^^ this useState shadows startDate
5 |
6 | useEffect(() => {
7 | setLocalStartDate(startDate);
error.shadowed-props-with-onchange.ts:7:8
5 |
6 | useEffect(() => {
> 7 | setLocalStartDate(startDate);
| ^^^^^^^^^^^^^^^^^ this setState synchronizes the state
8 | }, [startDate]);
9 |
10 | const onChange = (date) => {
error.shadowed-props-with-onchange.ts:11:8
9 |
10 | const onChange = (date) => {
> 11 | setLocalStartDate(date);
| ^^^^^^^^^^^^^^^^^ this setState updates the shadowed state, but should call an onChange event from the parent
12 | onStartDateChange(date);
13 | }
14 | return <DateInput value={localStartDate} second={endDate} onChange={onChange} />
```

View File

@@ -1,15 +0,0 @@
// @validateNoDerivedComputationsInEffects
function EndDate({startDate, endDate, onStartDateChange}) {
const [localStartDate, setLocalStartDate] = useState(startDate);
useEffect(() => {
setLocalStartDate(startDate);
}, [startDate]);
const onChange = (date) => {
setLocalStartDate(date);
onStartDateChange(date);
}
return <DateInput value={localStartDate} second={endDate} onChange={onChange} />
}

View File

@@ -1,4 +0,0 @@
yarn run v1.22.22
$ ./scripts/link-react-compiler-runtime.sh && yarn snap:ci
success Using linked package for "react-compiler-runtime".
$ yarn snap:build && yarn snap

View File

@@ -1969,44 +1969,6 @@ function createModel(response: Response, model: any): any {
return model;
}
const mightHaveStaticConstructor = /\bclass\b.*\bstatic\b/;
function getInferredFunctionApproximate(code: string): () => void {
let slicedCode;
if (code.startsWith('Object.defineProperty(')) {
slicedCode = code.slice('Object.defineProperty('.length);
} else if (code.startsWith('(')) {
slicedCode = code.slice(1);
} else {
slicedCode = code;
}
if (slicedCode.startsWith('async function')) {
const idx = slicedCode.indexOf('(', 14);
if (idx !== -1) {
const name = slicedCode.slice(14, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':async function(){}})')[
name
];
}
} else if (slicedCode.startsWith('function')) {
const idx = slicedCode.indexOf('(', 8);
if (idx !== -1) {
const name = slicedCode.slice(8, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':function(){}})')[name];
}
} else if (slicedCode.startsWith('class')) {
const idx = slicedCode.indexOf('{', 5);
if (idx !== -1) {
const name = slicedCode.slice(5, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[name];
}
}
return function () {};
}
function parseModelString(
response: Response,
parentObject: Object,
@@ -2196,37 +2158,41 @@ function parseModelString(
// This should not compile to eval() because then it has local scope access.
const code = value.slice(2);
try {
// If this might be a class constructor with a static initializer or
// static constructor then don't eval it. It might cause unexpected
// side-effects. Instead, fallback to parsing out the function type
// and name.
if (!mightHaveStaticConstructor.test(code)) {
// eslint-disable-next-line no-eval
return (0, eval)(code);
}
// eslint-disable-next-line no-eval
return (0, eval)(code);
} catch (x) {
// Fallthrough to fallback case.
}
// We currently use this to express functions so we fail parsing it,
// let's just return a blank function as a place holder.
let fn;
try {
fn = getInferredFunctionApproximate(code);
if (code.startsWith('Object.defineProperty(')) {
const DESCRIPTOR = ',"name",{value:"';
const idx = code.lastIndexOf(DESCRIPTOR);
// We currently use this to express functions so we fail parsing it,
// let's just return a blank function as a place holder.
if (code.startsWith('(async function')) {
const idx = code.indexOf('(', 15);
if (idx !== -1) {
const name = JSON.parse(
code.slice(idx + DESCRIPTOR.length - 1, code.length - 2),
);
// $FlowFixMe[cannot-write]
Object.defineProperty(fn, 'name', {value: name});
const name = code.slice(15, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)(
'({' + JSON.stringify(name) + ':async function(){}})',
)[name];
}
} else if (code.startsWith('(function')) {
const idx = code.indexOf('(', 9);
if (idx !== -1) {
const name = code.slice(9, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)(
'({' + JSON.stringify(name) + ':function(){}})',
)[name];
}
} else if (code.startsWith('(class')) {
const idx = code.indexOf('{', 6);
if (idx !== -1) {
const name = code.slice(6, idx).trim();
// eslint-disable-next-line no-eval
return (0, eval)('({' + JSON.stringify(name) + ':class{}})')[
name
];
}
}
} catch (_) {
fn = function () {};
return function () {};
}
return fn;
}
// Fallthrough
}

View File

@@ -3239,8 +3239,6 @@ describe('ReactFlight', () => {
}
Object.defineProperty(MyClass.prototype, 'y', {enumerable: true});
Object.defineProperty(MyClass, 'name', {value: 'MyClassName'});
function ServerComponent() {
console.log('hi', {
prop: 123,
@@ -3343,7 +3341,6 @@ describe('ReactFlight', () => {
const instance = mockConsoleLog.mock.calls[0][1].instance;
expect(typeof Class).toBe('function');
expect(Class.prototype.constructor).toBe(Class);
expect(Class.name).toBe('MyClassName');
expect(instance instanceof Class).toBe(true);
expect(Object.getPrototypeOf(instance)).toBe(Class.prototype);
expect(instance.x).toBe(1);

View File

@@ -862,7 +862,6 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "render()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
@@ -904,7 +903,6 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "createRoot()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,
@@ -945,7 +943,6 @@ describe('ProfilingCache', () => {
{
"compiledWithForget": false,
"displayName": "createRoot()",
"env": null,
"hocDisplayNames": null,
"id": 1,
"key": null,

View File

@@ -4818,7 +4818,6 @@ export function attach(
displayName: getDisplayNameForFiber(fiber) || 'Anonymous',
id: instance.id,
key: fiber.key,
env: null,
type: getElementTypeForFiber(fiber),
};
} else {
@@ -4827,7 +4826,6 @@ export function attach(
displayName: componentInfo.name || 'Anonymous',
id: instance.id,
key: componentInfo.key == null ? null : componentInfo.key,
env: componentInfo.env == null ? null : componentInfo.env,
type: ElementTypeVirtual,
};
}
@@ -5453,8 +5451,6 @@ export function attach(
// List of owners
owners,
env: null,
rootType,
rendererPackageName: renderer.rendererPackageName,
rendererVersion: renderer.version,
@@ -5558,8 +5554,6 @@ export function attach(
// List of owners
owners,
env: componentInfo.env == null ? null : componentInfo.env,
rootType,
rendererPackageName: renderer.rendererPackageName,
rendererVersion: renderer.version,

View File

@@ -795,7 +795,6 @@ export function attach(
displayName: getData(owner).displayName || 'Unknown',
id: getID(owner),
key: element.key,
env: null,
type: getElementType(owner),
});
if (owner._currentElement) {
@@ -858,8 +857,6 @@ export function attach(
// List of owners
owners,
env: null,
rootType: null,
rendererPackageName: null,
rendererVersion: null,

View File

@@ -256,7 +256,6 @@ export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
env: null | string,
type: ElementType,
};
@@ -302,10 +301,6 @@ export type InspectedElement = {
// List of owners
owners: Array<SerializedElement> | null,
// Environment name that this component executed in or null for the client
env: string | null,
source: ReactFunctionLocation | null,
type: ElementType,

View File

@@ -255,7 +255,6 @@ export function convertInspectedElementBackendToFrontend(
id,
type,
owners,
env,
source,
context,
hooks,
@@ -300,7 +299,6 @@ export function convertInspectedElementBackendToFrontend(
owners === null
? null
: owners.map(backendToFrontendSerializedElementMapper),
env,
context: hydrateHelper(context),
hooks: hydrateHelper(hooks),
props: hydrateHelper(props),

View File

@@ -16,21 +16,18 @@ import styles from './ElementBadges.css';
type Props = {
hocDisplayNames: Array<string> | null,
environmentName: string | null,
compiledWithForget: boolean,
className?: string,
};
export default function ElementBadges({
compiledWithForget,
environmentName,
hocDisplayNames,
className = '',
}: Props): React.Node {
if (
!compiledWithForget &&
(hocDisplayNames == null || hocDisplayNames.length === 0) &&
environmentName == null
(hocDisplayNames == null || hocDisplayNames.length === 0)
) {
return null;
}
@@ -39,8 +36,6 @@ export default function ElementBadges({
<div className={`${styles.Root} ${className}`}>
{compiledWithForget && <ForgetBadge indexable={false} />}
{environmentName != null ? <Badge>{environmentName}</Badge> : null}
{hocDisplayNames != null && hocDisplayNames.length > 0 && (
<Badge>{hocDisplayNames[0]}</Badge>
)}

View File

@@ -150,28 +150,13 @@ function SuspendedByRow({
</Button>
{isOpen && (
<div className={styles.CollapsableContent}>
{showIOStack && (
<StackTraceView
stack={ioInfo.stack}
environmentName={
ioOwner !== null && ioOwner.env === ioInfo.env
? null
: ioInfo.env
}
/>
)}
{showIOStack && <StackTraceView stack={ioInfo.stack} />}
{(showIOStack || !showAwaitStack) &&
ioOwner !== null &&
ioOwner.id !== inspectedElement.id ? (
<OwnerView
key={ioOwner.id}
displayName={ioOwner.displayName || 'Anonymous'}
environmentName={
ioOwner.env === inspectedElement.env &&
ioOwner.env === ioInfo.env
? null
: ioOwner.env
}
hocDisplayNames={ioOwner.hocDisplayNames}
compiledWithForget={ioOwner.compiledWithForget}
id={ioOwner.id}
@@ -183,25 +168,12 @@ function SuspendedByRow({
<>
<div className={styles.SmallHeader}>awaited at:</div>
{asyncInfo.stack !== null && asyncInfo.stack.length > 0 && (
<StackTraceView
stack={asyncInfo.stack}
environmentName={
asyncOwner !== null && asyncOwner.env === asyncInfo.env
? null
: asyncInfo.env
}
/>
<StackTraceView stack={asyncInfo.stack} />
)}
{asyncOwner !== null && asyncOwner.id !== inspectedElement.id ? (
<OwnerView
key={asyncOwner.id}
displayName={asyncOwner.displayName || 'Anonymous'}
environmentName={
asyncOwner.env === inspectedElement.env &&
asyncOwner.env === asyncInfo.env
? null
: asyncOwner.env
}
hocDisplayNames={asyncOwner.hocDisplayNames}
compiledWithForget={asyncOwner.compiledWithForget}
id={asyncOwner.id}

View File

@@ -174,9 +174,6 @@ export default function InspectedElementView({
key={owner.id}
displayName={owner.displayName || 'Anonymous'}
hocDisplayNames={owner.hocDisplayNames}
environmentName={
inspectedElement.env === owner.env ? null : owner.env
}
compiledWithForget={owner.compiledWithForget}
id={owner.id}
isInStore={store.containsElement(owner.id)}

View File

@@ -20,7 +20,6 @@ import styles from './OwnerView.css';
type OwnerViewProps = {
displayName: string,
hocDisplayNames: Array<string> | null,
environmentName: string | null,
compiledWithForget: boolean,
id: number,
isInStore: boolean,
@@ -28,7 +27,6 @@ type OwnerViewProps = {
export default function OwnerView({
displayName,
environmentName,
hocDisplayNames,
compiledWithForget,
id,
@@ -67,7 +65,6 @@ export default function OwnerView({
<ElementBadges
hocDisplayNames={hocDisplayNames}
compiledWithForget={compiledWithForget}
environmentName={environmentName}
/>
</span>
</Button>

View File

@@ -220,7 +220,6 @@ function ElementsDropdown({owners, selectOwner}: ElementsDropdownProps) {
<ElementBadges
hocDisplayNames={owner.hocDisplayNames}
environmentName={owner.env}
compiledWithForget={owner.compiledWithForget}
className={styles.BadgesBlock}
/>
@@ -269,7 +268,6 @@ function ElementView({isSelected, owner, selectOwner}: ElementViewProps) {
<ElementBadges
hocDisplayNames={hocDisplayNames}
environmentName={owner.env}
compiledWithForget={compiledWithForget}
className={styles.BadgesBlock}
/>

View File

@@ -12,8 +12,6 @@ import {use, useContext} from 'react';
import useOpenResource from '../useOpenResource';
import ElementBadges from './ElementBadges';
import styles from './StackTraceView.css';
import type {
@@ -30,13 +28,9 @@ import formatLocationForDisplay from './formatLocationForDisplay';
type CallSiteViewProps = {
callSite: ReactCallSite,
environmentName: null | string,
};
export function CallSiteView({
callSite,
environmentName,
}: CallSiteViewProps): React.Node {
export function CallSiteView({callSite}: CallSiteViewProps): React.Node {
const fetchFileWithCaching = useContext(FetchFileWithCachingContext);
const [virtualFunctionName, virtualURL, virtualLine, virtualColumn] =
@@ -70,33 +64,19 @@ export function CallSiteView({
title={url + ':' + line}>
{formatLocationForDisplay(url, line, column)}
</span>
<ElementBadges environmentName={environmentName} />
</div>
);
}
type Props = {
stack: ReactStackTrace,
environmentName: null | string,
};
export default function StackTraceView({
stack,
environmentName,
}: Props): React.Node {
export default function StackTraceView({stack}: Props): React.Node {
return (
<div className={styles.StackTraceView}>
{stack.map((callSite, index) => (
<CallSiteView
key={index}
callSite={callSite}
environmentName={
// Badge last row
// TODO: If we start ignore listing the last row, we should badge the last
// non-ignored row.
index === stack.length - 1 ? environmentName : null
}
/>
<CallSiteView key={index} callSite={callSite} />
))}
</div>
);

View File

@@ -954,7 +954,7 @@ function TreeContextController({
Array<number>,
Map<number, number>,
]) => {
dispatch({
transitionDispatch({
type: 'HANDLE_STORE_MUTATION',
payload: [addedElementIDs, removedElementIDs],
});
@@ -965,7 +965,7 @@ function TreeContextController({
// At the moment, we can treat this as a mutation.
// We don't know which Elements were newly added/removed, but that should be okay in this case.
// It would only impact the search state, which is unlikely to exist yet at this point.
dispatch({
transitionDispatch({
type: 'HANDLE_STORE_MUTATION',
payload: [[], new Map()],
});

View File

@@ -208,7 +208,6 @@ export type SerializedElement = {
displayName: string | null,
id: number,
key: number | string | null,
env: null | string,
hocDisplayNames: Array<string> | null,
compiledWithForget: boolean,
type: ElementType,
@@ -266,9 +265,6 @@ export type InspectedElement = {
// List of owners
owners: Array<SerializedElement> | null,
// Environment name that this component executed in or null for the client
env: string | null,
// Location of component in source code.
source: ReactFunctionLocation | null,

View File

@@ -158,7 +158,6 @@ import {
DefaultHydrationLane,
SomeRetryLane,
includesSomeLane,
includesOnlyRetries,
laneToLanes,
removeLanes,
mergeLanes,
@@ -270,7 +269,6 @@ import {
scheduleUpdateOnFiber,
renderDidSuspendDelayIfPossible,
markSkippedUpdateLanes,
markRenderDerivedCause,
getWorkInProgressRoot,
peekDeferredLane,
} from './ReactFiberWorkLoop';
@@ -948,13 +946,6 @@ function updateDehydratedActivityComponent(
// but after we've already committed once.
warnIfHydrating();
if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
markRenderDerivedCause(workInProgress);
}
if (
// TODO: Factoring is a little weird, since we check this right below, too.
!didReceiveUpdate
@@ -1141,16 +1132,6 @@ function updateActivityComponent(
children: nextChildren,
};
if (
includesSomeLane(renderLanes, (OffscreenLane: Lane)) &&
includesSomeLane(renderLanes, current.lanes)
) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
markRenderDerivedCause(workInProgress);
}
const primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentChild,
offscreenChildProps,
@@ -2534,17 +2515,6 @@ function updateSuspenseComponent(
workInProgress.memoizedState = SUSPENDED_MARKER;
return fallbackChildFragment;
} else {
if (
prevState !== null &&
includesOnlyRetries(renderLanes) &&
includesSomeLane(renderLanes, current.lanes)
) {
// If we're rendering Retry lanes and we're entering the primary content then it's possible
// that the only reason we rendered was because we left this boundary to be warmed up but
// nothing else scheduled an update. If so, use it as the cause of the render.
markRenderDerivedCause(workInProgress);
}
pushPrimaryTreeSuspenseHandler(workInProgress);
const nextPrimaryChildren = nextProps.children;
@@ -2903,13 +2873,6 @@ function updateDehydratedSuspenseComponent(
// but after we've already committed once.
warnIfHydrating();
if (includesSomeLane(renderLanes, (OffscreenLane: Lane))) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
markRenderDerivedCause(workInProgress);
}
if (isSuspenseInstanceFallback(suspenseInstance)) {
// This boundary is in a permanent fallback state. In this case, we'll never
// get an update and we'll never be able to hydrate the final content. Let's just try the

View File

@@ -122,10 +122,7 @@ import {
markStateUpdateScheduled,
setIsStrictModeForDevtools,
} from './ReactFiberDevToolsHook';
import {
startUpdateTimerByLane,
startHostActionTimer,
} from './ReactProfilerTimer';
import {startUpdateTimerByLane} from './ReactProfilerTimer';
import {createCache} from './ReactFiberCacheComponent';
import {
createUpdate as createLegacyQueueUpdate,
@@ -3242,8 +3239,6 @@ export function startHostTransition<F>(
BasicStateAction<Thenable<TransitionStatus> | TransitionStatus>,
> = stateHook.queue;
startHostActionTimer(formFiber);
startTransition(
formFiber,
queue,

View File

@@ -609,7 +609,6 @@ export function logBlockingStart(
eventType: null | string,
eventIsRepeat: boolean,
isSpawnedUpdate: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask, // DEV-only
@@ -659,13 +658,11 @@ export function logBlockingStart(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
isPingedUpdate
? 'Promise Resolved'
: isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -675,13 +672,11 @@ export function logBlockingStart(
);
} else {
console.timeStamp(
isPingedUpdate
? 'Promise Resolved'
: isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
isSpawnedUpdate
? 'Cascading Update'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -699,7 +694,6 @@ export function logTransitionStart(
eventTime: number,
eventType: null | string,
eventIsRepeat: boolean,
isPingedUpdate: boolean,
renderStartTime: number,
debugTask: null | ConsoleTask, // DEV-only
): void {
@@ -769,11 +763,7 @@ export function logTransitionStart(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
isPingedUpdate
? 'Promise Resolved'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -783,11 +773,7 @@ export function logTransitionStart(
);
} else {
console.timeStamp(
isPingedUpdate
? 'Promise Resolved'
: renderStartTime - updateTime > 5
? 'Update Blocked'
: 'Update',
renderStartTime - updateTime > 5 ? 'Update Blocked' : 'Update',
updateTime,
renderStartTime,
currentTrack,
@@ -803,43 +789,23 @@ export function logRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
const label = includesOnlyOffscreenLanes(lanes)
? 'Prepared'
: includesOnlyHydrationLanes(lanes)
? 'Hydrated'
: 'Render';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
includesOnlyOffscreenLanes(lanes)
? 'Prepared'
: includesOnlyHydrationLanes(lanes)
? 'Hydrated'
: 'Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -847,43 +813,23 @@ export function logInterruptedRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
const label = includesOnlyOffscreenLanes(lanes)
? 'Prewarm'
: includesOnlyHydrationLanes(lanes)
? 'Interrupted Hydration'
: 'Interrupted Render';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
label,
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
includesOnlyOffscreenLanes(lanes)
? 'Prewarm'
: includesOnlyHydrationLanes(lanes)
? 'Interrupted Hydration'
: 'Interrupted Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -891,38 +837,19 @@ export function logSuspendedRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
'Prewarm',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -930,39 +857,20 @@ export function logSuspendedWithDelayPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
// This means the render was suspended and cannot commit until it gets unblocked.
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
const color = includesOnlyHydrationOrOffscreenLanes(lanes)
? 'tertiary-dark'
: 'primary-dark';
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
),
);
} else {
console.timeStamp(
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
console.timeStamp(
'Suspended',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
color,
);
}
}
@@ -972,12 +880,8 @@ export function logRecoveredRenderPhase(
lanes: Lanes,
recoverableErrors: Array<CapturedValue<mixed>>,
hydrationFailed: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < recoverableErrors.length; i++) {
@@ -993,7 +897,7 @@ export function logRecoveredRenderPhase(
String(error);
properties.push(['Recoverable Error', message]);
}
const options = {
performance.measure('Recovered', {
start: startTime,
end: endTime,
detail: {
@@ -1007,15 +911,7 @@ export function logRecoveredRenderPhase(
properties,
},
},
};
if (debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
performance.measure.bind(performance, 'Recovered', options),
);
} else {
performance.measure('Recovered', options);
}
});
} else {
console.timeStamp(
'Recovered',
@@ -1033,144 +929,68 @@ export function logErroredRenderPhase(
startTime: number,
endTime: number,
lanes: Lanes,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
),
);
} else {
console.timeStamp(
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
console.timeStamp(
'Errored',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
export function logInconsistentRender(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
),
);
} else {
console.timeStamp(
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
console.timeStamp(
'Teared Render',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'error',
);
}
}
export function logSuspenseThrottlePhase(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
// This was inside a throttled Suspense boundary commit.
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Throttled',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
'Throttled',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
console.timeStamp(
'Throttled',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
export function logSuspendedCommitPhase(
startTime: number,
endTime: number,
debugTask: null | ConsoleTask,
): void {
// This means the commit was suspended on CSS or images.
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
// TODO: Include the exact reason and URLs of what resources suspended.
// TODO: This might also be Suspended while waiting on a View Transition.
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Suspended on CSS or Images',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
'Suspended on CSS or Images',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
console.timeStamp(
'Suspended on CSS or Images',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
@@ -1179,12 +999,8 @@ export function logCommitErrored(
endTime: number,
errors: Array<CapturedValue<mixed>>,
passive: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__) {
const properties: Array<[string, string]> = [];
for (let i = 0; i < errors.length; i++) {
@@ -1200,7 +1016,7 @@ export function logCommitErrored(
String(error);
properties.push(['Error', message]);
}
const options = {
performance.measure('Errored', {
start: startTime,
end: endTime,
detail: {
@@ -1214,15 +1030,7 @@ export function logCommitErrored(
properties,
},
},
};
if (debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
performance.measure.bind(performance, 'Errored', options),
);
} else {
performance.measure('Errored', options);
}
});
} else {
console.timeStamp(
'Errored',
@@ -1240,39 +1048,20 @@ export function logCommitPhase(
startTime: number,
endTime: number,
errors: null | Array<CapturedValue<mixed>>,
debugTask: null | ConsoleTask,
): void {
if (errors !== null) {
logCommitErrored(startTime, endTime, errors, false, debugTask);
logCommitErrored(startTime, endTime, errors, false);
return;
}
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
),
);
} else {
console.timeStamp(
'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
console.timeStamp(
'Commit',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
}
@@ -1280,35 +1069,16 @@ export function logPaintYieldPhase(
startTime: number,
endTime: number,
delayedUntilPaint: boolean,
debugTask: null | ConsoleTask,
): void {
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
delayedUntilPaint ? 'Waiting for Paint' : '',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
),
);
} else {
console.timeStamp(
delayedUntilPaint ? 'Waiting for Paint' : '',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
console.timeStamp(
delayedUntilPaint ? 'Waiting for Paint' : '',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-light',
);
}
}
@@ -1316,38 +1086,19 @@ export function logPassiveCommitPhase(
startTime: number,
endTime: number,
errors: null | Array<CapturedValue<mixed>>,
debugTask: null | ConsoleTask,
): void {
if (errors !== null) {
logCommitErrored(startTime, endTime, errors, true, debugTask);
logCommitErrored(startTime, endTime, errors, true);
return;
}
if (supportsUserTiming) {
if (endTime <= startTime) {
return;
}
if (__DEV__ && debugTask) {
debugTask.run(
// $FlowFixMe[method-unbinding]
console.timeStamp.bind(
console,
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
),
);
} else {
console.timeStamp(
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
console.timeStamp(
'Remaining Effects',
startTime,
endTime,
currentTrack,
LANES_TRACK_GROUP,
'secondary-dark',
);
}
}

View File

@@ -346,7 +346,6 @@ export function createHydrationContainer(
update.callback =
callback !== undefined && callback !== null ? callback : null;
enqueueUpdate(current, update, lane);
startUpdateTimerByLane(lane, 'hydrateRoot()');
scheduleInitialHydrationOnRoot(root, lane);
return root;

View File

@@ -266,16 +266,15 @@ import {
blockingClampTime,
blockingUpdateTime,
blockingUpdateTask,
blockingUpdateType,
blockingEventTime,
blockingEventType,
blockingEventIsRepeat,
blockingSpawnedUpdate,
blockingSuspendedTime,
transitionClampTime,
transitionStartTime,
transitionUpdateTime,
transitionUpdateTask,
transitionUpdateType,
transitionEventTime,
transitionEventType,
transitionEventIsRepeat,
@@ -302,8 +301,6 @@ import {
startPingTimerByLanes,
recordEffectError,
resetCommitErrors,
PINGED_UPDATE,
SPAWNED_UPDATE,
} from './ReactProfilerTimer';
// DEV stuff
@@ -485,9 +482,6 @@ export function getWorkInProgressTransitions(): null | Array<Transition> {
return workInProgressTransitions;
}
// The first setState call that eventually caused the current render.
let workInProgressUpdateTask: null | ConsoleTask = null;
let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null;
let currentEndTime: number | null = null;
@@ -1110,11 +1104,7 @@ export function performWorkOnRoot(
) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logInconsistentRender(
renderStartTime,
renderEndTime,
workInProgressUpdateTask,
);
logInconsistentRender(renderStartTime, renderEndTime);
finalizeRender(lanes, renderEndTime);
}
// A store was mutated in an interleaved event. Render again,
@@ -1140,12 +1130,7 @@ export function performWorkOnRoot(
if (errorRetryLanes !== NoLanes) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logErroredRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
logErroredRenderPhase(renderStartTime, renderEndTime, lanes);
finalizeRender(lanes, renderEndTime);
}
lanes = errorRetryLanes;
@@ -1176,12 +1161,7 @@ export function performWorkOnRoot(
if (exitStatus === RootFatalErrored) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logErroredRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
logErroredRenderPhase(renderStartTime, renderEndTime, lanes);
finalizeRender(lanes, renderEndTime);
}
prepareFreshStack(root, NoLanes);
@@ -1322,12 +1302,7 @@ function finishConcurrentRender(
// until we receive more data.
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logSuspendedRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
logSuspendedRenderPhase(renderStartTime, renderEndTime, lanes);
finalizeRender(lanes, renderEndTime);
trackSuspendedTime(lanes, renderEndTime);
}
@@ -1892,22 +1867,18 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
} else {
logInterruptedRenderPhase(
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
}
workInProgressUpdateTask = null;
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
workInProgressUpdateTask = blockingUpdateTask;
const clampedUpdateTime =
blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
? blockingClampTime
@@ -1927,7 +1898,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
? clampedUpdateTime
: renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
logBlockingStart(
@@ -1935,8 +1905,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
clampedEventTime,
blockingEventType,
blockingEventIsRepeat,
blockingUpdateType === SPAWNED_UPDATE,
blockingUpdateType === PINGED_UPDATE,
blockingSpawnedUpdate,
renderStartTime,
lanes,
blockingUpdateTask,
@@ -1944,7 +1913,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
clearBlockingTimers();
}
if (includesTransitionLane(lanes)) {
workInProgressUpdateTask = transitionUpdateTask;
const clampedStartTime =
transitionStartTime >= 0 && transitionStartTime < transitionClampTime
? transitionClampTime
@@ -1968,7 +1936,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
? clampedUpdateTime
: renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
logTransitionStart(
@@ -1977,7 +1944,6 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
clampedEventTime,
transitionEventType,
transitionEventIsRepeat,
transitionUpdateType === PINGED_UPDATE,
renderStartTime,
transitionUpdateTask,
);
@@ -2261,21 +2227,6 @@ function popAsyncDispatcher(prevAsyncDispatcher: any) {
ReactSharedInternals.A = prevAsyncDispatcher;
}
export function markRenderDerivedCause(fiber: Fiber): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (__DEV__) {
if (workInProgressUpdateTask === null) {
// If we don't have a cause associated with this render, it's likely because some
// other render left work behind on this Fiber. The real cause is this Fiber itself.
// We use its debugTask as the cause for this render. This might not be the only
// one when multiple siblings are rendered but they ideally shouldn't be.
workInProgressUpdateTask =
fiber._debugTask == null ? null : fiber._debugTask;
}
}
}
}
export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
@@ -3288,7 +3239,6 @@ function commitRoot(
completedRenderStartTime,
completedRenderEndTime,
lanes,
workInProgressUpdateTask,
);
} else if (recoverableErrors !== null) {
const hydrationFailed =
@@ -3302,15 +3252,9 @@ function commitRoot(
lanes,
recoverableErrors,
hydrationFailed,
workInProgressUpdateTask,
);
} else {
logRenderPhase(
completedRenderStartTime,
completedRenderEndTime,
lanes,
workInProgressUpdateTask,
);
logRenderPhase(completedRenderStartTime, completedRenderEndTime, lanes);
}
}
@@ -3481,17 +3425,9 @@ function commitRoot(
recordCommitTime();
if (enableComponentPerformanceTrack) {
if (suspendedCommitReason === SUSPENDED_COMMIT) {
logSuspendedCommitPhase(
completedRenderEndTime,
commitStartTime,
workInProgressUpdateTask,
);
logSuspendedCommitPhase(completedRenderEndTime, commitStartTime);
} else if (suspendedCommitReason === THROTTLED_COMMIT) {
logSuspenseThrottlePhase(
completedRenderEndTime,
commitStartTime,
workInProgressUpdateTask,
);
logSuspenseThrottlePhase(completedRenderEndTime, commitStartTime);
}
}
}
@@ -3736,7 +3672,6 @@ function flushSpawnedWork(): void {
: commitStartTime,
commitEndTime,
commitErrors,
workInProgressUpdateTask,
);
}
@@ -4212,7 +4147,6 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
commitEndTime,
passiveEffectStartTime,
!!wasDelayedCommit,
workInProgressUpdateTask,
);
}
@@ -4248,7 +4182,6 @@ function flushPassiveEffectsImpl(wasDelayedCommit: void | boolean) {
passiveEffectStartTime,
passiveEffectsEndTime,
commitErrors,
workInProgressUpdateTask,
);
finalizeRender(lanes, passiveEffectsEndTime);
}

View File

@@ -48,11 +48,6 @@ const createTask =
console.createTask
: (name: string) => null;
export const REGULAR_UPDATE: UpdateType = 0;
export const SPAWNED_UPDATE: UpdateType = 1;
export const PINGED_UPDATE: UpdateType = 2;
export opaque type UpdateType = 0 | 1 | 2;
export let renderStartTime: number = -0;
export let commitStartTime: number = -0;
export let commitEndTime: number = -0;
@@ -67,16 +62,15 @@ export let componentEffectErrors: null | Array<CapturedValue<mixed>> = null;
export let blockingClampTime: number = -0;
export let blockingUpdateTime: number = -1.1; // First sync setState scheduled.
export let blockingUpdateTask: null | ConsoleTask = null; // First sync setState's stack trace.
export let blockingUpdateType: UpdateType = 0;
export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
export let blockingEventType: null | string = null; // Event type of the first setState.
export let blockingEventIsRepeat: boolean = false;
export let blockingSpawnedUpdate: boolean = false;
export let blockingSuspendedTime: number = -1.1;
// TODO: This should really be one per Transition lane.
export let transitionClampTime: number = -0;
export let transitionStartTime: number = -1.1; // First startTransition call before setState.
export let transitionUpdateTime: number = -1.1; // First transition setState scheduled.
export let transitionUpdateType: UpdateType = 0;
export let transitionUpdateTask: null | ConsoleTask = null; // First transition setState's stack trace.
export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
export let transitionEventType: null | string = null; // Event type of the first transition.
@@ -103,7 +97,7 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
blockingUpdateTime = now();
blockingUpdateTask = createTask(method);
if (isAlreadyRendering()) {
blockingUpdateType = SPAWNED_UPDATE;
blockingSpawnedUpdate = true;
}
const newEventTime = resolveEventTimeStamp();
const newEventType = resolveEventType();
@@ -116,7 +110,7 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
// If this is a second update in the same event, we treat it as a spawned update.
// This might be a microtask spawned from useEffect, multiple flushSync or
// a setState in a microtask spawned after the first setState. Regardless it's bad.
blockingUpdateType = SPAWNED_UPDATE;
blockingSpawnedUpdate = true;
}
blockingEventTime = newEventTime;
blockingEventType = newEventType;
@@ -141,54 +135,6 @@ export function startUpdateTimerByLane(lane: Lane, method: string): void {
}
}
export function startHostActionTimer(fiber: Fiber): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
// This schedules an update on both the blocking lane for the pending state and on the
// transition lane for the action update. Using the debug task from the host fiber.
if (blockingUpdateTime < 0) {
blockingUpdateTime = now();
blockingUpdateTask =
__DEV__ && fiber._debugTask != null ? fiber._debugTask : null;
if (isAlreadyRendering()) {
blockingUpdateType = SPAWNED_UPDATE;
}
const newEventTime = resolveEventTimeStamp();
const newEventType = resolveEventType();
if (
newEventTime !== blockingEventTime ||
newEventType !== blockingEventType
) {
blockingEventIsRepeat = false;
} else if (newEventType !== null) {
// If this is a second update in the same event, we treat it as a spawned update.
// This might be a microtask spawned from useEffect, multiple flushSync or
// a setState in a microtask spawned after the first setState. Regardless it's bad.
blockingUpdateType = SPAWNED_UPDATE;
}
blockingEventTime = newEventTime;
blockingEventType = newEventType;
}
if (transitionUpdateTime < 0) {
transitionUpdateTime = now();
transitionUpdateTask =
__DEV__ && fiber._debugTask != null ? fiber._debugTask : null;
if (transitionStartTime < 0) {
const newEventTime = resolveEventTimeStamp();
const newEventType = resolveEventType();
if (
newEventTime !== transitionEventTime ||
newEventType !== transitionEventType
) {
transitionEventIsRepeat = false;
}
transitionEventTime = newEventTime;
transitionEventType = newEventType;
}
}
}
export function startPingTimerByLanes(lanes: Lanes): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
@@ -199,14 +145,10 @@ export function startPingTimerByLanes(lanes: Lanes): void {
if (includesSyncLane(lanes) || includesBlockingLane(lanes)) {
if (blockingUpdateTime < 0) {
blockingClampTime = blockingUpdateTime = now();
blockingUpdateTask = createTask('Promise Resolved');
blockingUpdateType = PINGED_UPDATE;
}
} else if (includesTransitionLane(lanes)) {
if (transitionUpdateTime < 0) {
transitionClampTime = transitionUpdateTime = now();
transitionUpdateTask = createTask('Promise Resolved');
transitionUpdateType = PINGED_UPDATE;
}
}
}
@@ -224,9 +166,10 @@ export function trackSuspendedTime(lanes: Lanes, renderEndTime: number) {
export function clearBlockingTimers(): void {
blockingUpdateTime = -1.1;
blockingUpdateType = 0;
blockingUpdateTask = null;
blockingSuspendedTime = -1.1;
blockingEventIsRepeat = true;
blockingSpawnedUpdate = false;
}
export function startAsyncTransitionTimer(): void {
@@ -253,6 +196,20 @@ export function hasScheduledTransitionWork(): boolean {
return transitionUpdateTime > -1;
}
// We use this marker to indicate that we have scheduled a render to be performed
// but it's not an explicit state update.
const ACTION_STATE_MARKER = -0.5;
export function startActionStateUpdate(): void {
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
return;
}
if (transitionUpdateTime < 0) {
transitionUpdateTime = ACTION_STATE_MARKER;
transitionUpdateTask = null;
}
}
export function clearAsyncTransitionTimer(): void {
transitionStartTime = -1.1;
}
@@ -260,7 +217,7 @@ export function clearAsyncTransitionTimer(): void {
export function clearTransitionTimers(): void {
transitionStartTime = -1.1;
transitionUpdateTime = -1.1;
transitionUpdateType = 0;
transitionUpdateTask = null;
transitionSuspendedTime = -1.1;
transitionEventIsRepeat = true;
}

View File

@@ -4848,18 +4848,9 @@ function renderDebugModel(
return existingReference;
}
// $FlowFixMe[method-unbinding]
const functionBody: string = Function.prototype.toString.call(value);
const name = value.name;
const serializedValue = serializeEval(
typeof name === 'string'
? 'Object.defineProperty(' +
functionBody +
',"name",{value:' +
JSON.stringify(name) +
'})'
: '(' + functionBody + ')',
// $FlowFixMe[method-unbinding]
'(' + Function.prototype.toString.call(value) + ')',
);
request.pendingDebugChunks++;
const id = request.nextChunkId++;