Compare commits
1 Commits
rewrite-va
...
pr34121
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50850bbac6 |
@@ -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': {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
@@ -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>
|
||||
@@ -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'}],
|
||||
};
|
||||
@@ -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 (
|
||||
```
|
||||
|
||||
|
||||
@@ -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, '}],
|
||||
};
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -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}],
|
||||
};
|
||||
@@ -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 |
|
||||
```
|
||||
|
||||
|
||||
@@ -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'}],
|
||||
};
|
||||
@@ -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>;
|
||||
```
|
||||
|
||||
|
||||
@@ -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: ']'}],
|
||||
};
|
||||
@@ -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>;
|
||||
```
|
||||
|
||||
|
||||
@@ -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'}],
|
||||
};
|
||||
@@ -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>;
|
||||
```
|
||||
|
||||
|
||||
@@ -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'}],
|
||||
};
|
||||
@@ -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 (
|
||||
```
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// @validateNoDerivedComputationsInEffects
|
||||
|
||||
export default function InProductLobbyGeminiCard(
|
||||
input = 'empty',
|
||||
) {
|
||||
const [currInput, setCurrInput] = useState(input);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrInput(input)
|
||||
}, [input]);
|
||||
|
||||
return (
|
||||
<div>{currInput}</div>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
```
|
||||
|
||||
|
||||
@@ -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: [],
|
||||
};
|
||||
@@ -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} />
|
||||
```
|
||||
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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
|
||||
94
packages/react-client/src/ReactFlightClient.js
vendored
94
packages/react-client/src/ReactFlightClient.js
vendored
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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()],
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
13
packages/react-server/src/ReactFlightServer.js
vendored
13
packages/react-server/src/ReactFlightServer.js
vendored
@@ -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++;
|
||||
|
||||
Reference in New Issue
Block a user