import type {
Destination,
Chunk,
PrecomputedChunk,
} from './ReactServerStreamConfig';
import type {
ReactNodeList,
ReactContext,
ReactConsumerType,
OffscreenMode,
Wakeable,
Thenable,
ReactFormState,
ReactComponentInfo,
ReactDebugInfo,
} from 'shared/ReactTypes';
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
import type {
RenderState,
ResumableState,
FormatContext,
HoistableState,
} from './ReactFizzConfig';
import type {ContextSnapshot} from './ReactFizzNewContext';
import type {ComponentStackNode} from './ReactFizzComponentStack';
import type {TreeContext} from './ReactFizzTreeContext';
import type {ThenableState} from './ReactFizzThenable';
import {describeObjectForErrorMessage} from 'shared/ReactSerializationErrors';
import {
scheduleWork,
scheduleMicrotask,
beginWriting,
writeChunk,
writeChunkAndReturn,
completeWriting,
flushBuffered,
close,
closeWithError,
} from './ReactServerStreamConfig';
import {
writeCompletedRoot,
writePlaceholder,
writeStartCompletedSuspenseBoundary,
writeStartPendingSuspenseBoundary,
writeStartClientRenderedSuspenseBoundary,
writeEndCompletedSuspenseBoundary,
writeEndPendingSuspenseBoundary,
writeEndClientRenderedSuspenseBoundary,
writeStartSegment,
writeEndSegment,
writeClientRenderBoundaryInstruction,
writeCompletedBoundaryInstruction,
writeCompletedSegmentInstruction,
writeHoistablesForBoundary,
pushTextInstance,
pushStartInstance,
pushEndInstance,
pushStartCompletedSuspenseBoundary,
pushEndCompletedSuspenseBoundary,
pushSegmentFinale,
getChildFormatContext,
writeHoistables,
writePreamble,
writePostamble,
hoistHoistables,
createHoistableState,
supportsRequestStorage,
requestStorage,
pushFormStateMarkerIsMatching,
pushFormStateMarkerIsNotMatching,
resetResumableState,
completeResumableState,
emitEarlyPreloads,
bindToConsole,
} from './ReactFizzConfig';
import {
constructClassInstance,
mountClassInstance,
} from './ReactFizzClassComponent';
import {
getMaskedContext,
processChildContext,
emptyContextObject,
} from './ReactFizzContext';
import {
readContext,
rootContextSnapshot,
switchContext,
getActiveContext,
pushProvider,
popProvider,
} from './ReactFizzNewContext';
import {
prepareToUseHooks,
prepareToUseThenableState,
finishHooks,
checkDidRenderIdHook,
resetHooksState,
HooksDispatcher,
currentResumableState,
setCurrentResumableState,
getThenableStateAfterSuspending,
unwrapThenable,
readPreviousThenableFromState,
getActionStateCount,
getActionStateMatchingIndex,
} from './ReactFizzHooks';
import {DefaultAsyncDispatcher} from './ReactFizzAsyncDispatcher';
import {
getStackByComponentStackNode,
getOwnerStackByComponentStackNodeInDev,
} from './ReactFizzComponentStack';
import {emptyTreeContext, pushTreeContext} from './ReactFizzTreeContext';
import {currentTaskInDEV, setCurrentTaskInDEV} from './ReactFizzCurrentTask';
import {
callLazyInitInDEV,
callComponentInDEV,
callRenderInDEV,
} from './ReactFizzCallUserSpace';
import {
getIteratorFn,
ASYNC_ITERATOR,
REACT_ELEMENT_TYPE,
REACT_PORTAL_TYPE,
REACT_LAZY_TYPE,
REACT_SUSPENSE_TYPE,
REACT_LEGACY_HIDDEN_TYPE,
REACT_DEBUG_TRACING_MODE_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_PROFILER_TYPE,
REACT_SUSPENSE_LIST_TYPE,
REACT_FRAGMENT_TYPE,
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_CONSUMER_TYPE,
REACT_SCOPE_TYPE,
REACT_OFFSCREEN_TYPE,
REACT_POSTPONE_TYPE,
} from 'shared/ReactSymbols';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
disableLegacyContext,
disableLegacyContextForFunctionComponents,
enableScopeAPI,
enableSuspenseAvoidThisFallbackFizz,
enableCache,
enablePostpone,
enableHalt,
enableRenderableContext,
enableRefAsProp,
disableDefaultPropsExceptForClasses,
enableAsyncIterableChildren,
disableStringRefs,
enableOwnerStacks,
} from 'shared/ReactFeatureFlags';
import assign from 'shared/assign';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import isArray from 'shared/isArray';
import {SuspenseException, getSuspendedThenable} from './ReactFizzThenable';
import type {Postpone} from 'react/src/ReactPostpone';
export type KeyNode = [
Root | KeyNode ,
string | null ,
string | number ,
];
type ResumeSlots =
| null
| number
| {[index: number]: number};
type ReplaySuspenseBoundary = [
string | null ,
string | number ,
Array<ReplayNode> ,
ResumeSlots ,
null | ReplayNode ,
number ,
];
type ReplayNode =
| [
string | null ,
string | number ,
Array<ReplayNode> ,
ResumeSlots ,
]
| ReplaySuspenseBoundary;
type PostponedHoles = {
workingMap: Map<KeyNode, ReplayNode>,
rootNodes: Array<ReplayNode>,
rootSlots: ResumeSlots,
};
type LegacyContext = {
[key: string]: any,
};
const CLIENT_RENDERED = 4;
type SuspenseBoundary = {
status: 0 | 1 | 4 | 5,
rootSegmentID: number,
parentFlushed: boolean,
pendingTasks: number,
completedSegments: Array<Segment>,
byteSize: number,
fallbackAbortableTasks: Set<Task>,
contentState: HoistableState,
fallbackState: HoistableState,
trackedContentKeyPath: null | KeyNode,
trackedFallbackNode: null | ReplayNode,
errorDigest: ?string,
errorMessage?: null | string,
errorStack?: null | string,
errorComponentStack?: null | string,
};
type RenderTask = {
replay: null,
node: ReactNodeList,
childIndex: number,
ping: () => void,
blockedBoundary: Root | SuspenseBoundary,
blockedSegment: Segment,
hoistableState: null | HoistableState,
abortSet: Set<Task>,
keyPath: Root | KeyNode,
formatContext: FormatContext,
context: ContextSnapshot,
treeContext: TreeContext,
componentStack: null | ComponentStackNode,
thenableState: null | ThenableState,
isFallback: boolean,
legacyContext: LegacyContext,
debugTask: null | ConsoleTask,
};
type ReplaySet = {
nodes: Array<ReplayNode>,
slots: ResumeSlots,
pendingTasks: number,
};
type ReplayTask = {
replay: ReplaySet,
node: ReactNodeList,
childIndex: number,
ping: () => void,
blockedBoundary: Root | SuspenseBoundary,
blockedSegment: null,
hoistableState: null | HoistableState,
abortSet: Set<Task>,
keyPath: Root | KeyNode,
formatContext: FormatContext,
context: ContextSnapshot,
treeContext: TreeContext,
componentStack: null | ComponentStackNode,
thenableState: null | ThenableState,
isFallback: boolean,
legacyContext: LegacyContext,
debugTask: null | ConsoleTask,
};
export type Task = RenderTask | ReplayTask;
const PENDING = 0;
const COMPLETED = 1;
const FLUSHED = 2;
const ABORTED = 3;
const ERRORED = 4;
const POSTPONED = 5;
const RENDERING = 6;
type Root = null;
type Segment = {
status: 0 | 1 | 2 | 3 | 4 | 5 | 6,
parentFlushed: boolean,
id: number,
+index: number,
+chunks: Array<Chunk | PrecomputedChunk>,
+children: Array<Segment>,
parentFormatContext: FormatContext,
+boundary: null | SuspenseBoundary,
lastPushedText: boolean,
textEmbedded: boolean,
};
const OPENING = 10;
const OPEN = 11;
const ABORTING = 12;
const CLOSING = 13;
const CLOSED = 14;
export opaque type Request = {
destination: null | Destination,
flushScheduled: boolean,
+resumableState: ResumableState,
+renderState: RenderState,
+rootFormatContext: FormatContext,
+progressiveChunkSize: number,
status: 10 | 11 | 12 | 13 | 14,
fatalError: mixed,
nextSegmentId: number,
allPendingTasks: number,
pendingRootTasks: number,
completedRootSegment: null | Segment,
abortableTasks: Set<Task>,
pingedTasks: Array<Task>,
clientRenderedBoundaries: Array<SuspenseBoundary>,
completedBoundaries: Array<SuspenseBoundary>,
partialBoundaries: Array<SuspenseBoundary>,
trackedPostpones: null | PostponedHoles,
onError: (error: mixed, errorInfo: ThrownInfo) => ?string,
onAllReady: () => void,
onShellReady: () => void,
onShellError: (error: mixed) => void,
onFatalError: (error: mixed) => void,
onPostpone: (reason: string, postponeInfo: ThrownInfo) => void,
formState: null | ReactFormState<any, any>,
didWarnForKey?: null | WeakSet<ComponentStackNode>,
};
const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800;
function defaultErrorHandler(error: mixed) {
if (
typeof error === 'object' &&
error !== null &&
typeof error.environmentName === 'string'
) {
bindToConsole('error', [error], error.environmentName)();
} else {
console['error'](error);
}
return null;
}
function noop(): void {}
function RequestInstance(
this: $FlowFixMe,
resumableState: ResumableState,
renderState: RenderState,
rootFormatContext: FormatContext,
progressiveChunkSize: void | number,
onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),
onAllReady: void | (() => void),
onShellReady: void | (() => void),
onShellError: void | ((error: mixed) => void),
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
formState: void | null | ReactFormState<any, any>,
) {
const pingedTasks: Array<Task> = [];
const abortSet: Set<Task> = new Set();
this.destination = null;
this.flushScheduled = false;
this.resumableState = resumableState;
this.renderState = renderState;
this.rootFormatContext = rootFormatContext;
this.progressiveChunkSize =
progressiveChunkSize === undefined
? DEFAULT_PROGRESSIVE_CHUNK_SIZE
: progressiveChunkSize;
this.status = OPENING;
this.fatalError = null;
this.nextSegmentId = 0;
this.allPendingTasks = 0;
this.pendingRootTasks = 0;
this.completedRootSegment = null;
this.abortableTasks = abortSet;
this.pingedTasks = pingedTasks;
this.clientRenderedBoundaries = ([]: Array<SuspenseBoundary>);
this.completedBoundaries = ([]: Array<SuspenseBoundary>);
this.partialBoundaries = ([]: Array<SuspenseBoundary>);
this.trackedPostpones = null;
this.onError = onError === undefined ? defaultErrorHandler : onError;
this.onPostpone = onPostpone === undefined ? noop : onPostpone;
this.onAllReady = onAllReady === undefined ? noop : onAllReady;
this.onShellReady = onShellReady === undefined ? noop : onShellReady;
this.onShellError = onShellError === undefined ? noop : onShellError;
this.onFatalError = onFatalError === undefined ? noop : onFatalError;
this.formState = formState === undefined ? null : formState;
if (__DEV__) {
this.didWarnForKey = null;
}
}
export function createRequest(
children: ReactNodeList,
resumableState: ResumableState,
renderState: RenderState,
rootFormatContext: FormatContext,
progressiveChunkSize: void | number,
onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),
onAllReady: void | (() => void),
onShellReady: void | (() => void),
onShellError: void | ((error: mixed) => void),
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
formState: void | null | ReactFormState<any, any>,
): Request {
const request: Request = new RequestInstance(
resumableState,
renderState,
rootFormatContext,
progressiveChunkSize,
onError,
onAllReady,
onShellReady,
onShellError,
onFatalError,
onPostpone,
formState,
);
const rootSegment = createPendingSegment(
request,
0,
null,
rootFormatContext,
false,
false,
);
rootSegment.parentFlushed = true;
const rootTask = createRenderTask(
request,
null,
children,
-1,
null,
rootSegment,
null,
request.abortableTasks,
null,
rootFormatContext,
rootContextSnapshot,
emptyTreeContext,
null,
false,
emptyContextObject,
null,
);
pushComponentStack(rootTask);
request.pingedTasks.push(rootTask);
return request;
}
export function createPrerenderRequest(
children: ReactNodeList,
resumableState: ResumableState,
renderState: RenderState,
rootFormatContext: FormatContext,
progressiveChunkSize: void | number,
onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),
onAllReady: void | (() => void),
onShellReady: void | (() => void),
onShellError: void | ((error: mixed) => void),
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
): Request {
const request = createRequest(
children,
resumableState,
renderState,
rootFormatContext,
progressiveChunkSize,
onError,
onAllReady,
onShellReady,
onShellError,
onFatalError,
onPostpone,
undefined,
);
request.trackedPostpones = {
workingMap: new Map(),
rootNodes: [],
rootSlots: null,
};
return request;
}
export function resumeRequest(
children: ReactNodeList,
postponedState: PostponedState,
renderState: RenderState,
onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),
onAllReady: void | (() => void),
onShellReady: void | (() => void),
onShellError: void | ((error: mixed) => void),
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
): Request {
const request: Request = new RequestInstance(
postponedState.resumableState,
renderState,
postponedState.rootFormatContext,
postponedState.progressiveChunkSize,
onError,
onAllReady,
onShellReady,
onShellError,
onFatalError,
onPostpone,
null,
);
request.nextSegmentId = postponedState.nextSegmentId;
if (typeof postponedState.replaySlots === 'number') {
const resumedId = postponedState.replaySlots;
const rootSegment = createPendingSegment(
request,
0,
null,
postponedState.rootFormatContext,
false,
false,
);
rootSegment.id = resumedId;
rootSegment.parentFlushed = true;
const rootTask = createRenderTask(
request,
null,
children,
-1,
null,
rootSegment,
null,
request.abortableTasks,
null,
postponedState.rootFormatContext,
rootContextSnapshot,
emptyTreeContext,
null,
false,
emptyContextObject,
null,
);
pushComponentStack(rootTask);
request.pingedTasks.push(rootTask);
return request;
}
const replay: ReplaySet = {
nodes: postponedState.replayNodes,
slots: postponedState.replaySlots,
pendingTasks: 0,
};
const rootTask = createReplayTask(
request,
null,
replay,
children,
-1,
null,
null,
request.abortableTasks,
null,
postponedState.rootFormatContext,
rootContextSnapshot,
emptyTreeContext,
null,
false,
emptyContextObject,
null,
);
pushComponentStack(rootTask);
request.pingedTasks.push(rootTask);
return request;
}
export function resumeAndPrerenderRequest(
children: ReactNodeList,
postponedState: PostponedState,
renderState: RenderState,
onError: void | ((error: mixed, errorInfo: ErrorInfo) => ?string),
onAllReady: void | (() => void),
onShellReady: void | (() => void),
onShellError: void | ((error: mixed) => void),
onFatalError: void | ((error: mixed) => void),
onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
): Request {
const request = resumeRequest(
children,
postponedState,
renderState,
onError,
onAllReady,
onShellReady,
onShellError,
onFatalError,
onPostpone,
);
request.trackedPostpones = {
workingMap: new Map(),
rootNodes: [],
rootSlots: null,
};
return request;
}
let currentRequest: null | Request = null;
export function resolveRequest(): null | Request {
if (currentRequest) return currentRequest;
if (supportsRequestStorage) {
const store = requestStorage.getStore();
if (store) return store;
}
return null;
}
function pingTask(request: Request, task: Task): void {
const pingedTasks = request.pingedTasks;
pingedTasks.push(task);
if (request.pingedTasks.length === 1) {
request.flushScheduled = request.destination !== null;
if (request.trackedPostpones !== null || request.status === OPENING) {
scheduleMicrotask(() => performWork(request));
} else {
scheduleWork(() => performWork(request));
}
}
}
function createSuspenseBoundary(
request: Request,
fallbackAbortableTasks: Set<Task>,
): SuspenseBoundary {
const boundary: SuspenseBoundary = {
status: PENDING,
rootSegmentID: -1,
parentFlushed: false,
pendingTasks: 0,
completedSegments: [],
byteSize: 0,
fallbackAbortableTasks,
errorDigest: null,
contentState: createHoistableState(),
fallbackState: createHoistableState(),
trackedContentKeyPath: null,
trackedFallbackNode: null,
};
if (__DEV__) {
boundary.errorMessage = null;
boundary.errorStack = null;
boundary.errorComponentStack = null;
}
return boundary;
}
function createRenderTask(
request: Request,
thenableState: ThenableState | null,
node: ReactNodeList,
childIndex: number,
blockedBoundary: Root | SuspenseBoundary,
blockedSegment: Segment,
hoistableState: null | HoistableState,
abortSet: Set<Task>,
keyPath: Root | KeyNode,
formatContext: FormatContext,
context: ContextSnapshot,
treeContext: TreeContext,
componentStack: null | ComponentStackNode,
isFallback: boolean,
legacyContext: LegacyContext,
debugTask: null | ConsoleTask,
): RenderTask {
request.allPendingTasks++;
if (blockedBoundary === null) {
request.pendingRootTasks++;
} else {
blockedBoundary.pendingTasks++;
}
const task: RenderTask = ({
replay: null,
node,
childIndex,
ping: () => pingTask(request, task),
blockedBoundary,
blockedSegment,
hoistableState,
abortSet,
keyPath,
formatContext,
context,
treeContext,
componentStack,
thenableState,
isFallback,
}: any);
if (!disableLegacyContext) {
task.legacyContext = legacyContext;
}
if (__DEV__ && enableOwnerStacks) {
task.debugTask = debugTask;
}
abortSet.add(task);
return task;
}
function createReplayTask(
request: Request,
thenableState: ThenableState | null,
replay: ReplaySet,
node: ReactNodeList,
childIndex: number,
blockedBoundary: Root | SuspenseBoundary,
hoistableState: null | HoistableState,
abortSet: Set<Task>,
keyPath: Root | KeyNode,
formatContext: FormatContext,
context: ContextSnapshot,
treeContext: TreeContext,
componentStack: null | ComponentStackNode,
isFallback: boolean,
legacyContext: LegacyContext,
debugTask: null | ConsoleTask,
): ReplayTask {
request.allPendingTasks++;
if (blockedBoundary === null) {
request.pendingRootTasks++;
} else {
blockedBoundary.pendingTasks++;
}
replay.pendingTasks++;
const task: ReplayTask = ({
replay,
node,
childIndex,
ping: () => pingTask(request, task),
blockedBoundary,
blockedSegment: null,
hoistableState,
abortSet,
keyPath,
formatContext,
context,
treeContext,
componentStack,
thenableState,
isFallback,
}: any);
if (!disableLegacyContext) {
task.legacyContext = legacyContext;
}
if (__DEV__ && enableOwnerStacks) {
task.debugTask = debugTask;
}
abortSet.add(task);
return task;
}
function createPendingSegment(
request: Request,
index: number,
boundary: null | SuspenseBoundary,
parentFormatContext: FormatContext,
lastPushedText: boolean,
textEmbedded: boolean,
): Segment {
return {
status: PENDING,
id: -1,
index,
parentFlushed: false,
chunks: [],
children: [],
parentFormatContext,
boundary,
lastPushedText,
textEmbedded,
};
}
function getCurrentStackInDEV(): string {
if (__DEV__) {
if (currentTaskInDEV === null || currentTaskInDEV.componentStack === null) {
return '';
}
if (enableOwnerStacks) {
return getOwnerStackByComponentStackNodeInDev(
currentTaskInDEV.componentStack,
);
}
return getStackByComponentStackNode(currentTaskInDEV.componentStack);
}
return '';
}
function getStackFromNode(stackNode: ComponentStackNode): string {
return getStackByComponentStackNode(stackNode);
}
function pushServerComponentStack(
task: Task,
debugInfo: void | null | ReactDebugInfo,
): void {
if (!__DEV__) {
throw new Error(
'pushServerComponentStack should never be called in production. This is a bug in React.',
);
}
if (debugInfo != null) {
const stack: ReactDebugInfo = debugInfo;
for (let i = 0; i < stack.length; i++) {
const componentInfo: ReactComponentInfo = (stack[i]: any);
if (typeof componentInfo.name !== 'string') {
continue;
}
if (enableOwnerStacks && componentInfo.debugStack === undefined) {
continue;
}
task.componentStack = {
parent: task.componentStack,
type: componentInfo,
owner: componentInfo.owner,
stack: enableOwnerStacks ? componentInfo.debugStack : null,
};
if (enableOwnerStacks) {
task.debugTask = (componentInfo.debugTask: any);
}
}
}
}
function pushComponentStack(task: Task): void {
const node = task.node;
if (typeof node === 'object' && node !== null) {
switch ((node: any).$$typeof) {
case REACT_ELEMENT_TYPE: {
const element: any = node;
const type = element.type;
const owner = __DEV__ ? element._owner : null;
const stack = __DEV__ && enableOwnerStacks ? element._debugStack : null;
if (__DEV__) {
pushServerComponentStack(task, element._debugInfo);
if (enableOwnerStacks) {
task.debugTask = element._debugTask;
}
}
task.componentStack = createComponentStackFromType(
task.componentStack,
type,
owner,
stack,
);
break;
}
case REACT_LAZY_TYPE: {
if (__DEV__) {
const lazyNode: LazyComponentType<any, any> = (node: any);
pushServerComponentStack(task, lazyNode._debugInfo);
}
break;
}
default: {
if (__DEV__) {
const maybeUsable: Object = node;
if (typeof maybeUsable.then === 'function') {
const thenable: Thenable<ReactNodeList> = (maybeUsable: any);
pushServerComponentStack(task, thenable._debugInfo);
}
}
}
}
}
}
function createComponentStackFromType(
parent: null | ComponentStackNode,
type: Function | string | symbol,
owner: null | ReactComponentInfo | ComponentStackNode,
stack: null | Error,
): ComponentStackNode {
if (__DEV__) {
return {
parent,
type,
owner,
stack,
};
}
return {
parent,
type,
};
}
type ThrownInfo = {
componentStack?: string,
};
export type ErrorInfo = ThrownInfo;
export type PostponeInfo = ThrownInfo;
function getThrownInfo(node: null | ComponentStackNode): ThrownInfo {
const errorInfo: ThrownInfo = {};
if (node) {
Object.defineProperty(errorInfo, 'componentStack', {
configurable: true,
enumerable: true,
get() {
const stack = getStackFromNode(node);
Object.defineProperty(errorInfo, 'componentStack', {
value: stack,
});
return stack;
},
});
}
return errorInfo;
}
function encodeErrorForBoundary(
boundary: SuspenseBoundary,
digest: ?string,
error: mixed,
thrownInfo: ThrownInfo,
wasAborted: boolean,
) {
boundary.errorDigest = digest;
if (__DEV__) {
let message, stack;
if (error instanceof Error) {
message = String(error.message);
stack = String(error.stack);
} else if (typeof error === 'object' && error !== null) {
message = describeObjectForErrorMessage(error);
stack = null;
} else {
message = String(error);
stack = null;
}
const prefix = wasAborted
? 'Switched to client rendering because the server rendering aborted due to:\n\n'
: 'Switched to client rendering because the server rendering errored:\n\n';
boundary.errorMessage = prefix + message;
boundary.errorStack = stack !== null ? prefix + stack : null;
boundary.errorComponentStack = thrownInfo.componentStack;
}
}
function logPostpone(
request: Request,
reason: string,
postponeInfo: ThrownInfo,
debugTask: null | ConsoleTask,
): void {
const onPostpone = request.onPostpone;
if (__DEV__ && enableOwnerStacks && debugTask) {
debugTask.run(onPostpone.bind(null, reason, postponeInfo));
} else {
onPostpone(reason, postponeInfo);
}
}
function logRecoverableError(
request: Request,
error: any,
errorInfo: ThrownInfo,
debugTask: null | ConsoleTask,
): ?string {
const onError = request.onError;
const errorDigest =
__DEV__ && enableOwnerStacks && debugTask
? debugTask.run(onError.bind(null, error, errorInfo))
: onError(error, errorInfo);
if (errorDigest != null && typeof errorDigest !== 'string') {
if (__DEV__) {
console.error(
'onError returned something with a type other than "string". onError should return a string and may return null or undefined but must not return anything else. It received something of type "%s" instead',
typeof errorDigest,
);
}
return;
}
return errorDigest;
}
function fatalError(
request: Request,
error: mixed,
errorInfo: ThrownInfo,
debugTask: null | ConsoleTask,
): void {
const onShellError = request.onShellError;
const onFatalError = request.onFatalError;
if (__DEV__ && enableOwnerStacks && debugTask) {
debugTask.run(onShellError.bind(null, error));
debugTask.run(onFatalError.bind(null, error));
} else {
onShellError(error);
onFatalError(error);
}
if (request.destination !== null) {
request.status = CLOSED;
closeWithError(request.destination, error);
} else {
request.status = CLOSING;
request.fatalError = error;
}
}
function renderSuspenseBoundary(
request: Request,
someTask: Task,
keyPath: KeyNode,
props: Object,
): void {
if (someTask.replay !== null) {
const prevKeyPath = someTask.keyPath;
someTask.keyPath = keyPath;
const content: ReactNodeList = props.children;
try {
renderNode(request, someTask, content, -1);
} finally {
someTask.keyPath = prevKeyPath;
}
return;
}
const task: RenderTask = someTask;
const prevKeyPath = task.keyPath;
const parentBoundary = task.blockedBoundary;
const parentHoistableState = task.hoistableState;
const parentSegment = task.blockedSegment;
const fallback: ReactNodeList = props.fallback;
const content: ReactNodeList = props.children;
const fallbackAbortSet: Set<Task> = new Set();
const newBoundary = createSuspenseBoundary(request, fallbackAbortSet);
if (request.trackedPostpones !== null) {
newBoundary.trackedContentKeyPath = keyPath;
}
const insertionIndex = parentSegment.chunks.length;
const boundarySegment = createPendingSegment(
request,
insertionIndex,
newBoundary,
task.formatContext,
false,
false,
);
parentSegment.children.push(boundarySegment);
parentSegment.lastPushedText = false;
const contentRootSegment = createPendingSegment(
request,
0,
null,
task.formatContext,
false,
false,
);
contentRootSegment.parentFlushed = true;
if (request.trackedPostpones !== null) {
const trackedPostpones = request.trackedPostpones;
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
const fallbackReplayNode: ReplayNode = [
fallbackKeyPath[1],
fallbackKeyPath[2],
([]: Array<ReplayNode>),
null,
];
trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode);
newBoundary.trackedFallbackNode = fallbackReplayNode;
task.blockedSegment = boundarySegment;
task.keyPath = fallbackKeyPath;
boundarySegment.status = RENDERING;
try {
renderNode(request, task, fallback, -1);
pushSegmentFinale(
boundarySegment.chunks,
request.renderState,
boundarySegment.lastPushedText,
boundarySegment.textEmbedded,
);
boundarySegment.status = COMPLETED;
} catch (thrownValue: mixed) {
if (request.status === ABORTING) {
boundarySegment.status = ABORTED;
} else {
boundarySegment.status = ERRORED;
}
throw thrownValue;
} finally {
task.blockedSegment = parentSegment;
task.keyPath = prevKeyPath;
}
const suspendedPrimaryTask = createRenderTask(
request,
null,
content,
-1,
newBoundary,
contentRootSegment,
newBoundary.contentState,
task.abortSet,
keyPath,
task.formatContext,
task.context,
task.treeContext,
task.componentStack,
task.isFallback,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
pushComponentStack(suspendedPrimaryTask);
request.pingedTasks.push(suspendedPrimaryTask);
} else {
task.blockedBoundary = newBoundary;
task.hoistableState = newBoundary.contentState;
task.blockedSegment = contentRootSegment;
task.keyPath = keyPath;
contentRootSegment.status = RENDERING;
try {
renderNode(request, task, content, -1);
pushSegmentFinale(
contentRootSegment.chunks,
request.renderState,
contentRootSegment.lastPushedText,
contentRootSegment.textEmbedded,
);
contentRootSegment.status = COMPLETED;
queueCompletedSegment(newBoundary, contentRootSegment);
if (newBoundary.pendingTasks === 0 && newBoundary.status === PENDING) {
newBoundary.status = COMPLETED;
return;
}
} catch (thrownValue: mixed) {
newBoundary.status = CLIENT_RENDERED;
let error: mixed;
if (request.status === ABORTING) {
contentRootSegment.status = ABORTED;
error = request.fatalError;
} else {
contentRootSegment.status = ERRORED;
error = thrownValue;
}
const thrownInfo = getThrownInfo(task.componentStack);
let errorDigest;
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(
request,
postponeInstance.message,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
errorDigest = 'POSTPONE';
} else {
errorDigest = logRecoverableError(
request,
error,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
encodeErrorForBoundary(
newBoundary,
errorDigest,
error,
thrownInfo,
false,
);
untrackBoundary(request, newBoundary);
} finally {
task.blockedBoundary = parentBoundary;
task.hoistableState = parentHoistableState;
task.blockedSegment = parentSegment;
task.keyPath = prevKeyPath;
}
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
const suspendedFallbackTask = createRenderTask(
request,
null,
fallback,
-1,
parentBoundary,
boundarySegment,
newBoundary.fallbackState,
fallbackAbortSet,
fallbackKeyPath,
task.formatContext,
task.context,
task.treeContext,
task.componentStack,
true,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
pushComponentStack(suspendedFallbackTask);
request.pingedTasks.push(suspendedFallbackTask);
}
}
function replaySuspenseBoundary(
request: Request,
task: ReplayTask,
keyPath: KeyNode,
props: Object,
id: number,
childNodes: Array<ReplayNode>,
childSlots: ResumeSlots,
fallbackNodes: Array<ReplayNode>,
fallbackSlots: ResumeSlots,
): void {
const prevKeyPath = task.keyPath;
const previousReplaySet: ReplaySet = task.replay;
const parentBoundary = task.blockedBoundary;
const parentHoistableState = task.hoistableState;
const content: ReactNodeList = props.children;
const fallback: ReactNodeList = props.fallback;
const fallbackAbortSet: Set<Task> = new Set();
const resumedBoundary = createSuspenseBoundary(request, fallbackAbortSet);
resumedBoundary.parentFlushed = true;
resumedBoundary.rootSegmentID = id;
task.blockedBoundary = resumedBoundary;
task.hoistableState = resumedBoundary.contentState;
task.keyPath = keyPath;
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
try {
renderNode(request, task, content, -1);
if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {
throw new Error(
"Couldn't find all resumable slots by key/index during replaying. " +
"The tree doesn't match so React will fallback to client rendering.",
);
}
task.replay.pendingTasks--;
if (
resumedBoundary.pendingTasks === 0 &&
resumedBoundary.status === PENDING
) {
resumedBoundary.status = COMPLETED;
request.completedBoundaries.push(resumedBoundary);
return;
}
} catch (error: mixed) {
resumedBoundary.status = CLIENT_RENDERED;
const thrownInfo = getThrownInfo(task.componentStack);
let errorDigest;
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(
request,
postponeInstance.message,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
errorDigest = 'POSTPONE';
} else {
errorDigest = logRecoverableError(
request,
error,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
encodeErrorForBoundary(
resumedBoundary,
errorDigest,
error,
thrownInfo,
false,
);
task.replay.pendingTasks--;
request.clientRenderedBoundaries.push(resumedBoundary);
} finally {
task.blockedBoundary = parentBoundary;
task.hoistableState = parentHoistableState;
task.replay = previousReplaySet;
task.keyPath = prevKeyPath;
}
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
const fallbackReplay = {
nodes: fallbackNodes,
slots: fallbackSlots,
pendingTasks: 0,
};
const suspendedFallbackTask = createReplayTask(
request,
null,
fallbackReplay,
fallback,
-1,
parentBoundary,
resumedBoundary.fallbackState,
fallbackAbortSet,
fallbackKeyPath,
task.formatContext,
task.context,
task.treeContext,
task.componentStack,
true,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
pushComponentStack(suspendedFallbackTask);
request.pingedTasks.push(suspendedFallbackTask);
}
function renderBackupSuspenseBoundary(
request: Request,
task: Task,
keyPath: KeyNode,
props: Object,
) {
const content = props.children;
const segment = task.blockedSegment;
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
if (segment === null) {
renderNode(request, task, content, -1);
} else {
pushStartCompletedSuspenseBoundary(segment.chunks);
renderNode(request, task, content, -1);
pushEndCompletedSuspenseBoundary(segment.chunks);
}
task.keyPath = prevKeyPath;
}
function renderHostElement(
request: Request,
task: Task,
keyPath: KeyNode,
type: string,
props: Object,
): void {
const segment = task.blockedSegment;
if (segment === null) {
const children = props.children;
const prevContext = task.formatContext;
const prevKeyPath = task.keyPath;
task.formatContext = getChildFormatContext(prevContext, type, props);
task.keyPath = keyPath;
renderNode(request, task, children, -1);
task.formatContext = prevContext;
task.keyPath = prevKeyPath;
} else {
const children = pushStartInstance(
segment.chunks,
type,
props,
request.resumableState,
request.renderState,
task.hoistableState,
task.formatContext,
segment.lastPushedText,
task.isFallback,
);
segment.lastPushedText = false;
const prevContext = task.formatContext;
const prevKeyPath = task.keyPath;
task.formatContext = getChildFormatContext(prevContext, type, props);
task.keyPath = keyPath;
renderNode(request, task, children, -1);
task.formatContext = prevContext;
task.keyPath = prevKeyPath;
pushEndInstance(
segment.chunks,
type,
props,
request.resumableState,
prevContext,
);
segment.lastPushedText = false;
}
}
function shouldConstruct(Component: any) {
return Component.prototype && Component.prototype.isReactComponent;
}
function renderWithHooks<Props, SecondArg>(
request: Request,
task: Task,
keyPath: KeyNode,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
): any {
const prevThenableState = task.thenableState;
task.thenableState = null;
const componentIdentity = {};
prepareToUseHooks(
request,
task,
keyPath,
componentIdentity,
prevThenableState,
);
let result;
if (__DEV__) {
result = callComponentInDEV(Component, props, secondArg);
} else {
result = Component(props, secondArg);
}
return finishHooks(Component, props, result, secondArg);
}
function finishClassComponent(
request: Request,
task: Task,
keyPath: KeyNode,
instance: any,
Component: any,
props: any,
): ReactNodeList {
let nextChildren;
if (__DEV__) {
nextChildren = (callRenderInDEV(instance): any);
} else {
nextChildren = instance.render();
}
if (request.status === ABORTING) {
throw null;
}
if (__DEV__) {
if (instance.props !== props) {
if (!didWarnAboutReassigningProps) {
console.error(
'It looks like %s is reassigning its own `this.props` while rendering. ' +
'This is not supported and can lead to confusing bugs.',
getComponentNameFromType(Component) || 'a component',
);
}
didWarnAboutReassigningProps = true;
}
}
if (!disableLegacyContext) {
const childContextTypes = Component.childContextTypes;
if (childContextTypes !== null && childContextTypes !== undefined) {
const previousContext = task.legacyContext;
const mergedContext = processChildContext(
instance,
Component,
previousContext,
childContextTypes,
);
task.legacyContext = mergedContext;
renderNodeDestructive(request, task, nextChildren, -1);
task.legacyContext = previousContext;
return;
}
}
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, nextChildren, -1);
task.keyPath = prevKeyPath;
}
export function resolveClassComponentProps(
Component: any,
baseProps: Object,
): Object {
let newProps = baseProps;
if (enableRefAsProp) {
if ('ref' in baseProps) {
newProps = ({}: any);
for (const propName in baseProps) {
if (propName !== 'ref') {
newProps[propName] = baseProps[propName];
}
}
}
}
const defaultProps = Component.defaultProps;
if (
defaultProps &&
disableDefaultPropsExceptForClasses
) {
if (newProps === baseProps) {
newProps = assign({}, newProps, baseProps);
}
for (const propName in defaultProps) {
if (newProps[propName] === undefined) {
newProps[propName] = defaultProps[propName];
}
}
}
return newProps;
}
function renderClassComponent(
request: Request,
task: Task,
keyPath: KeyNode,
Component: any,
props: any,
): void {
const resolvedProps = resolveClassComponentProps(Component, props);
const maskedContext = !disableLegacyContext
? getMaskedContext(Component, task.legacyContext)
: undefined;
const instance = constructClassInstance(
Component,
resolvedProps,
maskedContext,
);
mountClassInstance(instance, Component, resolvedProps, maskedContext);
finishClassComponent(
request,
task,
keyPath,
instance,
Component,
resolvedProps,
);
}
const didWarnAboutBadClass: {[string]: boolean} = {};
const didWarnAboutContextTypes: {[string]: boolean} = {};
const didWarnAboutContextTypeOnFunctionComponent: {[string]: boolean} = {};
const didWarnAboutGetDerivedStateOnFunctionComponent: {[string]: boolean} = {};
let didWarnAboutReassigningProps = false;
const didWarnAboutDefaultPropsOnFunctionComponent: {[string]: boolean} = {};
let didWarnAboutGenerators = false;
let didWarnAboutMaps = false;
function renderFunctionComponent(
request: Request,
task: Task,
keyPath: KeyNode,
Component: any,
props: any,
): void {
let legacyContext;
if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {
legacyContext = getMaskedContext(Component, task.legacyContext);
}
if (__DEV__) {
if (
Component.prototype &&
typeof Component.prototype.render === 'function'
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutBadClass[componentName]) {
console.error(
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
'This is likely to cause errors. Change %s to extend React.Component instead.',
componentName,
componentName,
);
didWarnAboutBadClass[componentName] = true;
}
}
}
const value = renderWithHooks(
request,
task,
keyPath,
Component,
props,
legacyContext,
);
if (request.status === ABORTING) {
throw null;
}
const hasId = checkDidRenderIdHook();
const actionStateCount = getActionStateCount();
const actionStateMatchingIndex = getActionStateMatchingIndex();
if (__DEV__) {
if (Component.contextTypes) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutContextTypes[componentName]) {
didWarnAboutContextTypes[componentName] = true;
if (disableLegacyContext) {
console.error(
'%s uses the legacy contextTypes API which was removed in React 19. ' +
'Use React.createContext() with React.useContext() instead. ' +
'(https://react.dev/link/legacy-context)',
componentName,
);
} else {
console.error(
'%s uses the legacy contextTypes API which will be removed soon. ' +
'Use React.createContext() with React.useContext() instead. ' +
'(https://react.dev/link/legacy-context)',
componentName,
);
}
}
}
}
if (__DEV__) {
validateFunctionComponentInDev(Component);
}
finishFunctionComponent(
request,
task,
keyPath,
value,
hasId,
actionStateCount,
actionStateMatchingIndex,
);
}
function finishFunctionComponent(
request: Request,
task: Task,
keyPath: KeyNode,
children: ReactNodeList,
hasId: boolean,
actionStateCount: number,
actionStateMatchingIndex: number,
) {
let didEmitActionStateMarkers = false;
if (actionStateCount !== 0 && request.formState !== null) {
const segment = task.blockedSegment;
if (segment === null) {
} else {
didEmitActionStateMarkers = true;
const target = segment.chunks;
for (let i = 0; i < actionStateCount; i++) {
if (i === actionStateMatchingIndex) {
pushFormStateMarkerIsMatching(target);
} else {
pushFormStateMarkerIsNotMatching(target);
}
}
}
}
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
if (hasId) {
const prevTreeContext = task.treeContext;
const totalChildren = 1;
const index = 0;
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, index);
renderNode(request, task, children, -1);
task.treeContext = prevTreeContext;
} else if (didEmitActionStateMarkers) {
renderNode(request, task, children, -1);
} else {
renderNodeDestructive(request, task, children, -1);
}
task.keyPath = prevKeyPath;
}
function validateFunctionComponentInDev(Component: any): void {
if (__DEV__) {
if (Component && Component.childContextTypes) {
console.error(
'childContextTypes cannot be defined on a function component.\n' +
' %s.childContextTypes = ...',
Component.displayName || Component.name || 'Component',
);
}
if (
!disableDefaultPropsExceptForClasses &&
Component.defaultProps !== undefined
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutDefaultPropsOnFunctionComponent[componentName]) {
console.error(
'%s: Support for defaultProps will be removed from function components ' +
'in a future major release. Use JavaScript default parameters instead.',
componentName,
);
didWarnAboutDefaultPropsOnFunctionComponent[componentName] = true;
}
}
if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support getDerivedStateFromProps.',
componentName,
);
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
}
}
if (
typeof Component.contextType === 'object' &&
Component.contextType !== null
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';
if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support contextType.',
componentName,
);
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
}
}
}
}
function resolveDefaultPropsOnNonClassComponent(
Component: any,
baseProps: Object,
): Object {
if (disableDefaultPropsExceptForClasses) {
return baseProps;
}
if (Component && Component.defaultProps) {
const props = assign({}, baseProps);
const defaultProps = Component.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return baseProps;
}
function renderForwardRef(
request: Request,
task: Task,
keyPath: KeyNode,
type: any,
props: Object,
ref: any,
): void {
let propsWithoutRef;
if (enableRefAsProp && 'ref' in props) {
propsWithoutRef = ({}: {[string]: any});
for (const key in props) {
if (key !== 'ref') {
propsWithoutRef[key] = props[key];
}
}
} else {
propsWithoutRef = props;
}
const children = renderWithHooks(
request,
task,
keyPath,
type.render,
propsWithoutRef,
ref,
);
const hasId = checkDidRenderIdHook();
const actionStateCount = getActionStateCount();
const actionStateMatchingIndex = getActionStateMatchingIndex();
finishFunctionComponent(
request,
task,
keyPath,
children,
hasId,
actionStateCount,
actionStateMatchingIndex,
);
}
function renderMemo(
request: Request,
task: Task,
keyPath: KeyNode,
type: any,
props: Object,
ref: any,
): void {
const innerType = type.type;
const resolvedProps = resolveDefaultPropsOnNonClassComponent(
innerType,
props,
);
renderElement(request, task, keyPath, innerType, resolvedProps, ref);
}
function renderContextConsumer(
request: Request,
task: Task,
keyPath: KeyNode,
context: ReactContext<any>,
props: Object,
): void {
const render = props.children;
if (__DEV__) {
if (typeof render !== 'function') {
console.error(
'A context consumer was rendered with multiple children, or a child ' +
"that isn't a function. A context consumer expects a single child " +
'that is a function. If you did pass a function, make sure there ' +
'is no trailing or leading whitespace around it.',
);
}
}
const newValue = readContext(context);
const newChildren = render(newValue);
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, newChildren, -1);
task.keyPath = prevKeyPath;
}
function renderContextProvider(
request: Request,
task: Task,
keyPath: KeyNode,
context: ReactContext<any>,
props: Object,
): void {
const value = props.value;
const children = props.children;
let prevSnapshot;
if (__DEV__) {
prevSnapshot = task.context;
}
const prevKeyPath = task.keyPath;
task.context = pushProvider(context, value);
task.keyPath = keyPath;
renderNodeDestructive(request, task, children, -1);
task.context = popProvider(context);
task.keyPath = prevKeyPath;
if (__DEV__) {
if (prevSnapshot !== task.context) {
console.error(
'Popping the context provider did not return back to the original snapshot. This is a bug in React.',
);
}
}
}
function renderLazyComponent(
request: Request,
task: Task,
keyPath: KeyNode,
lazyComponent: LazyComponentType<any, any>,
props: Object,
ref: any,
): void {
let Component;
if (__DEV__) {
Component = callLazyInitInDEV(lazyComponent);
} else {
const payload = lazyComponent._payload;
const init = lazyComponent._init;
Component = init(payload);
}
if (request.status === ABORTING) {
throw null;
}
const resolvedProps = resolveDefaultPropsOnNonClassComponent(
Component,
props,
);
renderElement(request, task, keyPath, Component, resolvedProps, ref);
}
function renderOffscreen(
request: Request,
task: Task,
keyPath: KeyNode,
props: Object,
): void {
const mode: ?OffscreenMode = (props.mode: any);
if (mode === 'hidden') {
} else {
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, props.children, -1);
task.keyPath = prevKeyPath;
}
}
function renderElement(
request: Request,
task: Task,
keyPath: KeyNode,
type: any,
props: Object,
ref: any,
): void {
if (typeof type === 'function') {
if (shouldConstruct(type)) {
renderClassComponent(request, task, keyPath, type, props);
return;
} else {
renderFunctionComponent(request, task, keyPath, type, props);
return;
}
}
if (typeof type === 'string') {
renderHostElement(request, task, keyPath, type, props);
return;
}
switch (type) {
case REACT_LEGACY_HIDDEN_TYPE:
case REACT_DEBUG_TRACING_MODE_TYPE:
case REACT_STRICT_MODE_TYPE:
case REACT_PROFILER_TYPE:
case REACT_FRAGMENT_TYPE: {
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, props.children, -1);
task.keyPath = prevKeyPath;
return;
}
case REACT_OFFSCREEN_TYPE: {
renderOffscreen(request, task, keyPath, props);
return;
}
case REACT_SUSPENSE_LIST_TYPE: {
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, props.children, -1);
task.keyPath = prevKeyPath;
return;
}
case REACT_SCOPE_TYPE: {
if (enableScopeAPI) {
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, props.children, -1);
task.keyPath = prevKeyPath;
return;
}
throw new Error('ReactDOMServer does not yet support scope components.');
}
case REACT_SUSPENSE_TYPE: {
if (
enableSuspenseAvoidThisFallbackFizz &&
props.unstable_avoidThisFallback === true
) {
renderBackupSuspenseBoundary(request, task, keyPath, props);
} else {
renderSuspenseBoundary(request, task, keyPath, props);
}
return;
}
}
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE: {
renderForwardRef(request, task, keyPath, type, props, ref);
return;
}
case REACT_MEMO_TYPE: {
renderMemo(request, task, keyPath, type, props, ref);
return;
}
case REACT_PROVIDER_TYPE: {
if (!enableRenderableContext) {
const context: ReactContext<any> = (type: any)._context;
renderContextProvider(request, task, keyPath, context, props);
return;
}
}
case REACT_CONTEXT_TYPE: {
if (enableRenderableContext) {
const context = type;
renderContextProvider(request, task, keyPath, context, props);
return;
} else {
let context: ReactContext<any> = (type: any);
if (__DEV__) {
if ((context: any)._context !== undefined) {
context = (context: any)._context;
}
}
renderContextConsumer(request, task, keyPath, context, props);
return;
}
}
case REACT_CONSUMER_TYPE: {
if (enableRenderableContext) {
const context: ReactContext<any> = (type: ReactConsumerType<any>)
._context;
renderContextConsumer(request, task, keyPath, context, props);
return;
}
}
case REACT_LAZY_TYPE: {
renderLazyComponent(request, task, keyPath, type, props, ref);
return;
}
}
}
let info = '';
if (__DEV__) {
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and " +
'named imports.';
}
}
throw new Error(
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
`but got: ${type == null ? type : typeof type}.${info}`,
);
}
function resumeNode(
request: Request,
task: ReplayTask,
segmentId: number,
node: ReactNodeList,
childIndex: number,
): void {
const prevReplay = task.replay;
const blockedBoundary = task.blockedBoundary;
const resumedSegment = createPendingSegment(
request,
0,
null,
task.formatContext,
false,
false,
);
resumedSegment.id = segmentId;
resumedSegment.parentFlushed = true;
try {
const renderTask: RenderTask = (task: any);
renderTask.replay = null;
renderTask.blockedSegment = resumedSegment;
renderNode(request, task, node, childIndex);
resumedSegment.status = COMPLETED;
if (blockedBoundary === null) {
request.completedRootSegment = resumedSegment;
} else {
queueCompletedSegment(blockedBoundary, resumedSegment);
if (blockedBoundary.parentFlushed) {
request.partialBoundaries.push(blockedBoundary);
}
}
} finally {
task.replay = prevReplay;
task.blockedSegment = null;
}
}
function replayElement(
request: Request,
task: ReplayTask,
keyPath: KeyNode,
name: null | string,
keyOrIndex: number | string,
childIndex: number,
type: any,
props: Object,
ref: any,
replay: ReplaySet,
): void {
const replayNodes = replay.nodes;
for (let i = 0; i < replayNodes.length; i++) {
const node = replayNodes[i];
if (keyOrIndex !== node[1]) {
continue;
}
if (node.length === 4) {
if (name !== null && name !== node[0]) {
throw new Error(
'Expected the resume to render <' +
(node[0]: any) +
'> in this slot but instead it rendered <' +
name +
'>. ' +
"The tree doesn't match so React will fallback to client rendering.",
);
}
const childNodes = node[2];
const childSlots = node[3];
const currentNode = task.node;
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
try {
renderElement(request, task, keyPath, type, props, ref);
if (
task.replay.pendingTasks === 1 &&
task.replay.nodes.length > 0
) {
throw new Error(
"Couldn't find all resumable slots by key/index during replaying. " +
"The tree doesn't match so React will fallback to client rendering.",
);
}
task.replay.pendingTasks--;
} catch (x) {
if (
typeof x === 'object' &&
x !== null &&
(x === SuspenseException || typeof x.then === 'function')
) {
if (task.node === currentNode) {
task.replay = replay;
}
throw x;
}
task.replay.pendingTasks--;
const thrownInfo = getThrownInfo(task.componentStack);
erroredReplay(
request,
task.blockedBoundary,
x,
thrownInfo,
childNodes,
childSlots,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
task.replay = replay;
} else {
if (type !== REACT_SUSPENSE_TYPE) {
const expectedType = 'Suspense';
throw new Error(
'Expected the resume to render <' +
expectedType +
'> in this slot but instead it rendered <' +
(getComponentNameFromType(type) || 'Unknown') +
'>. ' +
"The tree doesn't match so React will fallback to client rendering.",
);
}
replaySuspenseBoundary(
request,
task,
keyPath,
props,
node[5],
node[2],
node[3],
node[4] === null ? [] : node[4][2],
node[4] === null ? null : node[4][3],
);
}
replayNodes.splice(i, 1);
return;
}
}
function validateIterable(
task: Task,
iterable: Iterable<any>,
childIndex: number,
iterator: Iterator<any>,
iteratorFn: () => ?Iterator<any>,
): void {
if (__DEV__) {
if (iterator === iterable) {
const isGeneratorComponent =
childIndex === -1 &&
task.componentStack !== null &&
typeof task.componentStack.type === 'function' &&
Object.prototype.toString.call(task.componentStack.type) ===
'[object GeneratorFunction]' &&
Object.prototype.toString.call(iterator) === '[object Generator]';
if (!isGeneratorComponent) {
if (!didWarnAboutGenerators) {
console.error(
'Using Iterators as children is unsupported and will likely yield ' +
'unexpected results because enumerating a generator mutates it. ' +
'You may convert it to an array with `Array.from()` or the ' +
'`[...spread]` operator before rendering. You can also use an ' +
'Iterable that can iterate multiple times over the same items.',
);
}
didWarnAboutGenerators = true;
}
} else if ((iterable: any).entries === iteratorFn) {
if (!didWarnAboutMaps) {
console.error(
'Using Maps as children is not supported. ' +
'Use an array of keyed ReactElements instead.',
);
didWarnAboutMaps = true;
}
}
}
}
function validateAsyncIterable(
task: Task,
iterable: AsyncIterable<any>,
childIndex: number,
iterator: AsyncIterator<any>,
): void {
if (__DEV__) {
if (iterator === iterable) {
const isGeneratorComponent =
childIndex === -1 &&
task.componentStack !== null &&
typeof task.componentStack.type === 'function' &&
Object.prototype.toString.call(task.componentStack.type) ===
'[object AsyncGeneratorFunction]' &&
Object.prototype.toString.call(iterator) === '[object AsyncGenerator]';
if (!isGeneratorComponent) {
if (!didWarnAboutGenerators) {
console.error(
'Using AsyncIterators as children is unsupported and will likely yield ' +
'unexpected results because enumerating a generator mutates it. ' +
'You can use an AsyncIterable that can iterate multiple times over ' +
'the same items.',
);
}
didWarnAboutGenerators = true;
}
}
}
}
function warnOnFunctionType(invalidChild: Function) {
if (__DEV__) {
const name = invalidChild.displayName || invalidChild.name || 'Component';
console.error(
'Functions are not valid as a React child. This may happen if ' +
'you return %s instead of <%s /> from render. ' +
'Or maybe you meant to call this function rather than return it.',
name,
name,
);
}
}
function warnOnSymbolType(invalidChild: symbol) {
if (__DEV__) {
const name = String(invalidChild);
console.error('Symbols are not valid as a React child.\n' + ' %s', name);
}
}
function renderNodeDestructive(
request: Request,
task: Task,
node: ReactNodeList,
childIndex: number,
): void {
if (task.replay !== null && typeof task.replay.slots === 'number') {
const resumeSegmentID = task.replay.slots;
resumeNode(request, task, resumeSegmentID, node, childIndex);
return;
}
task.node = node;
task.childIndex = childIndex;
const previousComponentStack = task.componentStack;
const previousDebugTask =
__DEV__ && enableOwnerStacks ? task.debugTask : null;
pushComponentStack(task);
retryNode(request, task);
task.componentStack = previousComponentStack;
if (__DEV__ && enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
}
function retryNode(request: Request, task: Task): void {
const node = task.node;
const childIndex = task.childIndex;
if (node === null) {
return;
}
if (typeof node === 'object') {
switch ((node: any).$$typeof) {
case REACT_ELEMENT_TYPE: {
const element: any = node;
const type = element.type;
const key = element.key;
const props = element.props;
let ref;
if (enableRefAsProp) {
const refProp = props.ref;
ref = refProp !== undefined ? refProp : null;
} else {
ref = element.ref;
}
const debugTask: null | ConsoleTask =
__DEV__ && enableOwnerStacks ? task.debugTask : null;
const name = getComponentNameFromType(type);
const keyOrIndex =
key == null ? (childIndex === -1 ? 0 : childIndex) : key;
const keyPath = [task.keyPath, name, keyOrIndex];
if (task.replay !== null) {
if (debugTask) {
debugTask.run(
replayElement.bind(
null,
request,
task,
keyPath,
name,
keyOrIndex,
childIndex,
type,
props,
ref,
task.replay,
),
);
} else {
replayElement(
request,
task,
keyPath,
name,
keyOrIndex,
childIndex,
type,
props,
ref,
task.replay,
);
}
} else {
if (debugTask) {
debugTask.run(
renderElement.bind(
null,
request,
task,
keyPath,
type,
props,
ref,
),
);
} else {
renderElement(request, task, keyPath, type, props, ref);
}
}
return;
}
case REACT_PORTAL_TYPE:
throw new Error(
'Portals are not currently supported by the server renderer. ' +
'Render them conditionally so that they only appear on the client render.',
);
case REACT_LAZY_TYPE: {
const lazyNode: LazyComponentType<any, any> = (node: any);
let resolvedNode;
if (__DEV__) {
resolvedNode = callLazyInitInDEV(lazyNode);
} else {
const payload = lazyNode._payload;
const init = lazyNode._init;
resolvedNode = init(payload);
}
if (request.status === ABORTING) {
throw null;
}
renderNodeDestructive(request, task, resolvedNode, childIndex);
return;
}
}
if (isArray(node)) {
renderChildrenArray(request, task, node, childIndex);
return;
}
const iteratorFn = getIteratorFn(node);
if (iteratorFn) {
const iterator = iteratorFn.call(node);
if (iterator) {
if (__DEV__) {
validateIterable(task, node, childIndex, iterator, iteratorFn);
}
let step = iterator.next();
if (!step.done) {
const children = [];
do {
children.push(step.value);
step = iterator.next();
} while (!step.done);
renderChildrenArray(request, task, children, childIndex);
}
return;
}
}
if (
enableAsyncIterableChildren &&
typeof (node: any)[ASYNC_ITERATOR] === 'function'
) {
const iterator: AsyncIterator<ReactNodeList> = (node: any)[
ASYNC_ITERATOR
]();
if (iterator) {
if (__DEV__) {
validateAsyncIterable(task, (node: any), childIndex, iterator);
}
const prevThenableState = task.thenableState;
task.thenableState = null;
prepareToUseThenableState(prevThenableState);
const children = [];
let done = false;
if (iterator === node) {
let step = readPreviousThenableFromState();
while (step !== undefined) {
if (step.done) {
done = true;
break;
}
children.push(step.value);
step = readPreviousThenableFromState();
}
}
if (!done) {
let step = unwrapThenable(iterator.next());
while (!step.done) {
children.push(step.value);
step = unwrapThenable(iterator.next());
}
}
renderChildrenArray(request, task, children, childIndex);
return;
}
}
const maybeUsable: Object = node;
if (typeof maybeUsable.then === 'function') {
task.thenableState = null;
const thenable: Thenable<ReactNodeList> = (maybeUsable: any);
const result = renderNodeDestructive(
request,
task,
unwrapThenable(thenable),
childIndex,
);
return result;
}
if (maybeUsable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<ReactNodeList> = (maybeUsable: any);
return renderNodeDestructive(
request,
task,
readContext(context),
childIndex,
);
}
const childString = Object.prototype.toString.call(node);
throw new Error(
`Objects are not valid as a React child (found: ${
childString === '[object Object]'
? 'object with keys {' + Object.keys(node).join(', ') + '}'
: childString
}). ` +
'If you meant to render a collection of children, use an array ' +
'instead.',
);
}
if (typeof node === 'string') {
const segment = task.blockedSegment;
if (segment === null) {
} else {
segment.lastPushedText = pushTextInstance(
segment.chunks,
node,
request.renderState,
segment.lastPushedText,
);
}
return;
}
if (typeof node === 'number' || typeof node === 'bigint') {
const segment = task.blockedSegment;
if (segment === null) {
} else {
segment.lastPushedText = pushTextInstance(
segment.chunks,
'' + node,
request.renderState,
segment.lastPushedText,
);
}
return;
}
if (__DEV__) {
if (typeof node === 'function') {
warnOnFunctionType(node);
}
if (typeof node === 'symbol') {
warnOnSymbolType(node);
}
}
}
function replayFragment(
request: Request,
task: ReplayTask,
children: Array<any>,
childIndex: number,
): void {
const replay = task.replay;
const replayNodes = replay.nodes;
for (let j = 0; j < replayNodes.length; j++) {
const node = replayNodes[j];
if (node[1] !== childIndex) {
continue;
}
const childNodes = node[2];
const childSlots = node[3];
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
try {
renderChildrenArray(request, task, children, -1);
if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {
throw new Error(
"Couldn't find all resumable slots by key/index during replaying. " +
"The tree doesn't match so React will fallback to client rendering.",
);
}
task.replay.pendingTasks--;
} catch (x) {
if (
typeof x === 'object' &&
x !== null &&
(x === SuspenseException || typeof x.then === 'function')
) {
throw x;
}
task.replay.pendingTasks--;
const thrownInfo = getThrownInfo(task.componentStack);
erroredReplay(
request,
task.blockedBoundary,
x,
thrownInfo,
childNodes,
childSlots,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
task.replay = replay;
replayNodes.splice(j, 1);
break;
}
}
function warnForMissingKey(request: Request, task: Task, child: mixed): void {
if (__DEV__) {
if (
child === null ||
typeof child !== 'object' ||
(child.$$typeof !== REACT_ELEMENT_TYPE &&
child.$$typeof !== REACT_PORTAL_TYPE)
) {
return;
}
if (
!child._store ||
((child._store.validated || child.key != null) &&
child._store.validated !== 2)
) {
return;
}
if (typeof child._store !== 'object') {
throw new Error(
'React Component in warnForMissingKey should have a _store. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
}
child._store.validated = 1;
let didWarnForKey = request.didWarnForKey;
if (didWarnForKey == null) {
didWarnForKey = request.didWarnForKey = new WeakSet();
}
const parentStackFrame = task.componentStack;
if (parentStackFrame === null || didWarnForKey.has(parentStackFrame)) {
return;
}
didWarnForKey.add(parentStackFrame);
const componentName = getComponentNameFromType(child.type);
const childOwner = child._owner;
const parentOwner = parentStackFrame.owner;
let currentComponentErrorInfo = '';
if (parentOwner && typeof parentOwner.type !== 'undefined') {
const name = getComponentNameFromType(parentOwner.type);
if (name) {
currentComponentErrorInfo =
'\n\nCheck the render method of `' + name + '`.';
}
}
if (!currentComponentErrorInfo) {
if (componentName) {
currentComponentErrorInfo = `\n\nCheck the top-level render call using <${componentName}>.`;
}
}
let childOwnerAppendix = '';
if (childOwner != null && parentOwner !== childOwner) {
let ownerName = null;
if (typeof childOwner.type !== 'undefined') {
ownerName = getComponentNameFromType(childOwner.type);
} else if (typeof childOwner.name === 'string') {
ownerName = childOwner.name;
}
if (ownerName) {
childOwnerAppendix = ` It was passed a child from ${ownerName}.`;
}
}
const previousComponentStack = task.componentStack;
const stackFrame = createComponentStackFromType(
task.componentStack,
(child: any).type,
(child: any)._owner,
enableOwnerStacks ? (child: any)._debugStack : null,
);
task.componentStack = stackFrame;
console.error(
'Each child in a list should have a unique "key" prop.' +
'%s%s See https://react.dev/link/warning-keys for more information.',
currentComponentErrorInfo,
childOwnerAppendix,
);
task.componentStack = previousComponentStack;
}
}
function renderChildrenArray(
request: Request,
task: Task,
children: Array<any>,
childIndex: number,
): void {
const prevKeyPath = task.keyPath;
const previousComponentStack = task.componentStack;
let previousDebugTask = null;
if (__DEV__) {
if (enableOwnerStacks) {
previousDebugTask = task.debugTask;
}
pushServerComponentStack(task, (task.node: any)._debugInfo);
}
if (childIndex !== -1) {
task.keyPath = [task.keyPath, 'Fragment', childIndex];
if (task.replay !== null) {
replayFragment(
request,
task,
children,
childIndex,
);
task.keyPath = prevKeyPath;
if (__DEV__) {
task.componentStack = previousComponentStack;
if (enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
}
return;
}
}
const prevTreeContext = task.treeContext;
const totalChildren = children.length;
if (task.replay !== null) {
const resumeSlots = task.replay.slots;
if (resumeSlots !== null && typeof resumeSlots === 'object') {
for (let i = 0; i < totalChildren; i++) {
const node = children[i];
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
const resumeSegmentID = resumeSlots[i];
if (typeof resumeSegmentID === 'number') {
resumeNode(request, task, resumeSegmentID, node, i);
delete resumeSlots[i];
} else {
renderNode(request, task, node, i);
}
}
task.treeContext = prevTreeContext;
task.keyPath = prevKeyPath;
if (__DEV__) {
task.componentStack = previousComponentStack;
if (enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
}
return;
}
}
for (let i = 0; i < totalChildren; i++) {
const node = children[i];
if (__DEV__) {
warnForMissingKey(request, task, node);
}
task.treeContext = pushTreeContext(prevTreeContext, totalChildren, i);
renderNode(request, task, node, i);
}
task.treeContext = prevTreeContext;
task.keyPath = prevKeyPath;
if (__DEV__) {
task.componentStack = previousComponentStack;
if (enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
}
}
function trackPostpone(
request: Request,
trackedPostpones: PostponedHoles,
task: Task,
segment: Segment,
): void {
segment.status = POSTPONED;
const keyPath = task.keyPath;
const boundary = task.blockedBoundary;
if (boundary === null) {
segment.id = request.nextSegmentId++;
trackedPostpones.rootSlots = segment.id;
if (request.completedRootSegment !== null) {
request.completedRootSegment.status = POSTPONED;
}
return;
}
if (boundary !== null && boundary.status === PENDING) {
boundary.status = POSTPONED;
boundary.rootSegmentID = request.nextSegmentId++;
const boundaryKeyPath = boundary.trackedContentKeyPath;
if (boundaryKeyPath === null) {
throw new Error(
'It should not be possible to postpone at the root. This is a bug in React.',
);
}
const fallbackReplayNode = boundary.trackedFallbackNode;
const children: Array<ReplayNode> = [];
if (boundaryKeyPath === keyPath && task.childIndex === -1) {
if (segment.id === -1) {
if (segment.parentFlushed) {
segment.id = boundary.rootSegmentID;
} else {
segment.id = request.nextSegmentId++;
}
}
const boundaryNode: ReplaySuspenseBoundary = [
boundaryKeyPath[1],
boundaryKeyPath[2],
children,
segment.id,
fallbackReplayNode,
boundary.rootSegmentID,
];
trackedPostpones.workingMap.set(boundaryKeyPath, boundaryNode);
addToReplayParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
return;
} else {
let boundaryNode: void | ReplayNode =
trackedPostpones.workingMap.get(boundaryKeyPath);
if (boundaryNode === undefined) {
boundaryNode = [
boundaryKeyPath[1],
boundaryKeyPath[2],
children,
null,
fallbackReplayNode,
boundary.rootSegmentID,
];
trackedPostpones.workingMap.set(boundaryKeyPath, boundaryNode);
addToReplayParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
} else {
const suspenseBoundary: ReplaySuspenseBoundary = (boundaryNode: any);
suspenseBoundary[4] = fallbackReplayNode;
suspenseBoundary[5] = boundary.rootSegmentID;
}
}
}
if (segment.id === -1) {
if (segment.parentFlushed && boundary !== null) {
segment.id = boundary.rootSegmentID;
} else {
segment.id = request.nextSegmentId++;
}
}
if (task.childIndex === -1) {
if (keyPath === null) {
trackedPostpones.rootSlots = segment.id;
} else {
const workingMap = trackedPostpones.workingMap;
let resumableNode = workingMap.get(keyPath);
if (resumableNode === undefined) {
resumableNode = [
keyPath[1],
keyPath[2],
([]: Array<ReplayNode>),
segment.id,
];
addToReplayParent(resumableNode, keyPath[0], trackedPostpones);
} else {
resumableNode[3] = segment.id;
}
}
} else {
let slots;
if (keyPath === null) {
slots = trackedPostpones.rootSlots;
if (slots === null) {
slots = trackedPostpones.rootSlots = ({}: {[index: number]: number});
} else if (typeof slots === 'number') {
throw new Error(
'It should not be possible to postpone both at the root of an element ' +
'as well as a slot below. This is a bug in React.',
);
}
} else {
const workingMap = trackedPostpones.workingMap;
let resumableNode = workingMap.get(keyPath);
if (resumableNode === undefined) {
slots = ({}: {[index: number]: number});
resumableNode = ([
keyPath[1],
keyPath[2],
([]: Array<ReplayNode>),
slots,
]: ReplayNode);
workingMap.set(keyPath, resumableNode);
addToReplayParent(resumableNode, keyPath[0], trackedPostpones);
} else {
slots = resumableNode[3];
if (slots === null) {
slots = resumableNode[3] = ({}: {[index: number]: number});
} else if (typeof slots === 'number') {
throw new Error(
'It should not be possible to postpone both at the root of an element ' +
'as well as a slot below. This is a bug in React.',
);
}
}
}
slots[task.childIndex] = segment.id;
}
}
function untrackBoundary(request: Request, boundary: SuspenseBoundary) {
const trackedPostpones = request.trackedPostpones;
if (trackedPostpones === null) {
return;
}
const boundaryKeyPath = boundary.trackedContentKeyPath;
if (boundaryKeyPath === null) {
return;
}
const boundaryNode: void | ReplayNode =
trackedPostpones.workingMap.get(boundaryKeyPath);
if (boundaryNode === undefined) {
return;
}
boundaryNode.length = 4;
boundaryNode[2] = [];
boundaryNode[3] = null;
}
function injectPostponedHole(
request: Request,
task: RenderTask,
reason: string,
thrownInfo: ThrownInfo,
): Segment {
logPostpone(
request,
reason,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
const segment = task.blockedSegment;
const insertionIndex = segment.chunks.length;
const newSegment = createPendingSegment(
request,
insertionIndex,
null,
task.formatContext,
segment.lastPushedText,
true,
);
segment.children.push(newSegment);
segment.lastPushedText = false;
return newSegment;
}
function spawnNewSuspendedReplayTask(
request: Request,
task: ReplayTask,
thenableState: ThenableState | null,
): ReplayTask {
return createReplayTask(
request,
thenableState,
task.replay,
task.node,
task.childIndex,
task.blockedBoundary,
task.hoistableState,
task.abortSet,
task.keyPath,
task.formatContext,
task.context,
task.treeContext,
task.componentStack,
task.isFallback,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
function spawnNewSuspendedRenderTask(
request: Request,
task: RenderTask,
thenableState: ThenableState | null,
): RenderTask {
const segment = task.blockedSegment;
const insertionIndex = segment.chunks.length;
const newSegment = createPendingSegment(
request,
insertionIndex,
null,
task.formatContext,
segment.lastPushedText,
true,
);
segment.children.push(newSegment);
segment.lastPushedText = false;
return createRenderTask(
request,
thenableState,
task.node,
task.childIndex,
task.blockedBoundary,
newSegment,
task.hoistableState,
task.abortSet,
task.keyPath,
task.formatContext,
task.context,
task.treeContext,
task.componentStack,
task.isFallback,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
function renderNode(
request: Request,
task: Task,
node: ReactNodeList,
childIndex: number,
): void {
const previousFormatContext = task.formatContext;
const previousLegacyContext = !disableLegacyContext
? task.legacyContext
: emptyContextObject;
const previousContext = task.context;
const previousKeyPath = task.keyPath;
const previousTreeContext = task.treeContext;
const previousComponentStack = task.componentStack;
const previousDebugTask =
__DEV__ && enableOwnerStacks ? task.debugTask : null;
let x;
const segment = task.blockedSegment;
if (segment === null) {
try {
return renderNodeDestructive(request, task, node, childIndex);
} catch (thrownValue) {
resetHooksState();
x =
thrownValue === SuspenseException
?
getSuspendedThenable()
: thrownValue;
if (typeof x === 'object' && x !== null) {
if (typeof x.then === 'function') {
const wakeable: Wakeable = (x: any);
const thenableState = getThenableStateAfterSuspending();
const newTask = spawnNewSuspendedReplayTask(
request,
task,
thenableState,
);
const ping = newTask.ping;
wakeable.then(ping, ping);
task.formatContext = previousFormatContext;
if (!disableLegacyContext) {
task.legacyContext = previousLegacyContext;
}
task.context = previousContext;
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
if (__DEV__ && enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
switchContext(previousContext);
return;
}
if (x.message === 'Maximum call stack size exceeded') {
const thenableState = getThenableStateAfterSuspending();
const newTask = spawnNewSuspendedReplayTask(
request,
task,
thenableState,
);
request.pingedTasks.push(newTask);
task.formatContext = previousFormatContext;
if (!disableLegacyContext) {
task.legacyContext = previousLegacyContext;
}
task.context = previousContext;
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
if (__DEV__ && enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
switchContext(previousContext);
return;
}
}
}
} else {
const childrenLength = segment.children.length;
const chunkLength = segment.chunks.length;
try {
return renderNodeDestructive(request, task, node, childIndex);
} catch (thrownValue) {
resetHooksState();
segment.children.length = childrenLength;
segment.chunks.length = chunkLength;
x =
thrownValue === SuspenseException
?
getSuspendedThenable()
: thrownValue;
if (typeof x === 'object' && x !== null) {
if (typeof x.then === 'function') {
const wakeable: Wakeable = (x: any);
const thenableState = getThenableStateAfterSuspending();
const newTask = spawnNewSuspendedRenderTask(
request,
task,
thenableState,
);
const ping = newTask.ping;
wakeable.then(ping, ping);
task.formatContext = previousFormatContext;
if (!disableLegacyContext) {
task.legacyContext = previousLegacyContext;
}
task.context = previousContext;
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
if (__DEV__ && enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
switchContext(previousContext);
return;
}
if (
enablePostpone &&
x.$$typeof === REACT_POSTPONE_TYPE &&
request.trackedPostpones !== null &&
task.blockedBoundary !== null
) {
const trackedPostpones = request.trackedPostpones;
const postponeInstance: Postpone = (x: any);
const thrownInfo = getThrownInfo(task.componentStack);
const postponedSegment = injectPostponedHole(
request,
((task: any): RenderTask),
postponeInstance.message,
thrownInfo,
);
trackPostpone(request, trackedPostpones, task, postponedSegment);
task.formatContext = previousFormatContext;
if (!disableLegacyContext) {
task.legacyContext = previousLegacyContext;
}
task.context = previousContext;
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
if (__DEV__ && enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
switchContext(previousContext);
return;
}
if (x.message === 'Maximum call stack size exceeded') {
const thenableState = getThenableStateAfterSuspending();
const newTask = spawnNewSuspendedRenderTask(
request,
task,
thenableState,
);
request.pingedTasks.push(newTask);
task.formatContext = previousFormatContext;
if (!disableLegacyContext) {
task.legacyContext = previousLegacyContext;
}
task.context = previousContext;
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
if (__DEV__ && enableOwnerStacks) {
task.debugTask = previousDebugTask;
}
switchContext(previousContext);
return;
}
}
}
}
task.formatContext = previousFormatContext;
if (!disableLegacyContext) {
task.legacyContext = previousLegacyContext;
}
task.context = previousContext;
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
switchContext(previousContext);
throw x;
}
function erroredReplay(
request: Request,
boundary: Root | SuspenseBoundary,
error: mixed,
errorInfo: ThrownInfo,
replayNodes: ReplayNode[],
resumeSlots: ResumeSlots,
debugTask: null | ConsoleTask,
): void {
let errorDigest;
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(request, postponeInstance.message, errorInfo, debugTask);
errorDigest = 'POSTPONE';
} else {
errorDigest = logRecoverableError(request, error, errorInfo, debugTask);
}
abortRemainingReplayNodes(
request,
boundary,
replayNodes,
resumeSlots,
error,
errorDigest,
errorInfo,
false,
);
}
function erroredTask(
request: Request,
boundary: Root | SuspenseBoundary,
error: mixed,
errorInfo: ThrownInfo,
debugTask: null | ConsoleTask,
) {
let errorDigest;
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(request, postponeInstance.message, errorInfo, debugTask);
errorDigest = 'POSTPONE';
} else {
errorDigest = logRecoverableError(request, error, errorInfo, debugTask);
}
if (boundary === null) {
fatalError(request, error, errorInfo, debugTask);
} else {
boundary.pendingTasks--;
if (boundary.status !== CLIENT_RENDERED) {
boundary.status = CLIENT_RENDERED;
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, false);
untrackBoundary(request, boundary);
if (boundary.parentFlushed) {
request.clientRenderedBoundaries.push(boundary);
}
}
}
request.allPendingTasks--;
if (request.allPendingTasks === 0) {
completeAll(request);
}
}
function abortTaskSoft(this: Request, task: Task): void {
const request: Request = this;
const boundary = task.blockedBoundary;
const segment = task.blockedSegment;
if (segment !== null) {
segment.status = ABORTED;
finishedTask(request, boundary, segment);
}
}
function abortRemainingSuspenseBoundary(
request: Request,
rootSegmentID: number,
error: mixed,
errorDigest: ?string,
errorInfo: ThrownInfo,
wasAborted: boolean,
): void {
const resumedBoundary = createSuspenseBoundary(request, new Set());
resumedBoundary.parentFlushed = true;
resumedBoundary.rootSegmentID = rootSegmentID;
resumedBoundary.status = CLIENT_RENDERED;
encodeErrorForBoundary(
resumedBoundary,
errorDigest,
error,
errorInfo,
wasAborted,
);
if (resumedBoundary.parentFlushed) {
request.clientRenderedBoundaries.push(resumedBoundary);
}
}
function abortRemainingReplayNodes(
request: Request,
boundary: Root | SuspenseBoundary,
nodes: Array<ReplayNode>,
slots: ResumeSlots,
error: mixed,
errorDigest: ?string,
errorInfo: ThrownInfo,
aborted: boolean,
): void {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.length === 4) {
abortRemainingReplayNodes(
request,
boundary,
node[2],
node[3],
error,
errorDigest,
errorInfo,
aborted,
);
} else {
const boundaryNode: ReplaySuspenseBoundary = node;
const rootSegmentID = boundaryNode[5];
abortRemainingSuspenseBoundary(
request,
rootSegmentID,
error,
errorDigest,
errorInfo,
aborted,
);
}
}
nodes.length = 0;
if (slots !== null) {
if (boundary === null) {
throw new Error(
'We should not have any resumable nodes in the shell. ' +
'This is a bug in React.',
);
} else if (boundary.status !== CLIENT_RENDERED) {
boundary.status = CLIENT_RENDERED;
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, aborted);
if (boundary.parentFlushed) {
request.clientRenderedBoundaries.push(boundary);
}
}
if (typeof slots === 'object') {
for (const index in slots) {
delete slots[(index: any)];
}
}
}
}
function abortTask(task: Task, request: Request, error: mixed): void {
const boundary = task.blockedBoundary;
const segment = task.blockedSegment;
if (segment !== null) {
if (segment.status === RENDERING) {
return;
}
segment.status = ABORTED;
}
const errorInfo = getThrownInfo(task.componentStack);
if (boundary === null) {
if (request.status !== CLOSING && request.status !== CLOSED) {
const replay: null | ReplaySet = task.replay;
if (replay === null) {
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
const trackedPostpones = request.trackedPostpones;
if (trackedPostpones !== null && segment !== null) {
logPostpone(request, postponeInstance.message, errorInfo, null);
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, null, segment);
} else {
const fatal = new Error(
'The render was aborted with postpone when the shell is incomplete. Reason: ' +
postponeInstance.message,
);
logRecoverableError(request, fatal, errorInfo, null);
fatalError(request, fatal, errorInfo, null);
}
} else if (
enableHalt &&
request.trackedPostpones !== null &&
segment !== null
) {
const trackedPostpones = request.trackedPostpones;
logRecoverableError(request, error, errorInfo, null);
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, null, segment);
} else {
logRecoverableError(request, error, errorInfo, null);
fatalError(request, error, errorInfo, null);
}
return;
} else {
replay.pendingTasks--;
if (replay.pendingTasks === 0 && replay.nodes.length > 0) {
let errorDigest;
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(request, postponeInstance.message, errorInfo, null);
errorDigest = 'POSTPONE';
} else {
errorDigest = logRecoverableError(request, error, errorInfo, null);
}
abortRemainingReplayNodes(
request,
null,
replay.nodes,
replay.slots,
error,
errorDigest,
errorInfo,
true,
);
}
request.pendingRootTasks--;
if (request.pendingRootTasks === 0) {
completeShell(request);
}
}
}
} else {
boundary.pendingTasks--;
const trackedPostpones = request.trackedPostpones;
if (boundary.status !== CLIENT_RENDERED) {
if (enableHalt) {
if (trackedPostpones !== null && segment !== null) {
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(request, postponeInstance.message, errorInfo, null);
} else {
logRecoverableError(request, error, errorInfo, null);
}
trackPostpone(request, trackedPostpones, task, segment);
boundary.fallbackAbortableTasks.forEach(fallbackTask =>
abortTask(fallbackTask, request, error),
);
boundary.fallbackAbortableTasks.clear();
return finishedTask(request, boundary, segment);
}
}
boundary.status = CLIENT_RENDERED;
let errorDigest;
if (
enablePostpone &&
typeof error === 'object' &&
error !== null &&
error.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (error: any);
logPostpone(request, postponeInstance.message, errorInfo, null);
if (request.trackedPostpones !== null && segment !== null) {
trackPostpone(request, request.trackedPostpones, task, segment);
finishedTask(request, task.blockedBoundary, segment);
boundary.fallbackAbortableTasks.forEach(fallbackTask =>
abortTask(fallbackTask, request, error),
);
boundary.fallbackAbortableTasks.clear();
return;
}
errorDigest = 'POSTPONE';
} else {
errorDigest = logRecoverableError(request, error, errorInfo, null);
}
boundary.status = CLIENT_RENDERED;
encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, true);
untrackBoundary(request, boundary);
if (boundary.parentFlushed) {
request.clientRenderedBoundaries.push(boundary);
}
}
boundary.fallbackAbortableTasks.forEach(fallbackTask =>
abortTask(fallbackTask, request, error),
);
boundary.fallbackAbortableTasks.clear();
}
request.allPendingTasks--;
if (request.allPendingTasks === 0) {
completeAll(request);
}
}
function safelyEmitEarlyPreloads(
request: Request,
shellComplete: boolean,
): void {
try {
emitEarlyPreloads(
request.renderState,
request.resumableState,
shellComplete,
);
} catch (error) {
const errorInfo: ThrownInfo = {};
logRecoverableError(request, error, errorInfo, null);
}
}
function completeShell(request: Request) {
if (request.trackedPostpones === null) {
const shellComplete = true;
safelyEmitEarlyPreloads(request, shellComplete);
}
request.onShellError = noop;
const onShellReady = request.onShellReady;
onShellReady();
}
function completeAll(request: Request) {
const shellComplete =
request.trackedPostpones === null
?
true
:
request.completedRootSegment === null ||
request.completedRootSegment.status !== POSTPONED;
safelyEmitEarlyPreloads(request, shellComplete);
const onAllReady = request.onAllReady;
onAllReady();
}
function queueCompletedSegment(
boundary: SuspenseBoundary,
segment: Segment,
): void {
if (
segment.chunks.length === 0 &&
segment.children.length === 1 &&
segment.children[0].boundary === null &&
segment.children[0].id === -1
) {
const childSegment = segment.children[0];
childSegment.id = segment.id;
childSegment.parentFlushed = true;
if (childSegment.status === COMPLETED) {
queueCompletedSegment(boundary, childSegment);
}
} else {
const completedSegments = boundary.completedSegments;
completedSegments.push(segment);
}
}
function finishedTask(
request: Request,
boundary: Root | SuspenseBoundary,
segment: null | Segment,
) {
if (boundary === null) {
if (segment !== null && segment.parentFlushed) {
if (request.completedRootSegment !== null) {
throw new Error(
'There can only be one root segment. This is a bug in React.',
);
}
request.completedRootSegment = segment;
}
request.pendingRootTasks--;
if (request.pendingRootTasks === 0) {
completeShell(request);
}
} else {
boundary.pendingTasks--;
if (boundary.status === CLIENT_RENDERED) {
} else if (boundary.pendingTasks === 0) {
if (boundary.status === PENDING) {
boundary.status = COMPLETED;
}
if (segment !== null && segment.parentFlushed) {
if (segment.status === COMPLETED) {
queueCompletedSegment(boundary, segment);
}
}
if (boundary.parentFlushed) {
request.completedBoundaries.push(boundary);
}
if (boundary.status === COMPLETED) {
boundary.fallbackAbortableTasks.forEach(abortTaskSoft, request);
boundary.fallbackAbortableTasks.clear();
}
} else {
if (segment !== null && segment.parentFlushed) {
if (segment.status === COMPLETED) {
queueCompletedSegment(boundary, segment);
const completedSegments = boundary.completedSegments;
if (completedSegments.length === 1) {
if (boundary.parentFlushed) {
request.partialBoundaries.push(boundary);
}
}
}
}
}
}
request.allPendingTasks--;
if (request.allPendingTasks === 0) {
completeAll(request);
}
}
function retryTask(request: Request, task: Task): void {
const segment = task.blockedSegment;
if (segment === null) {
retryReplayTask(
request,
task,
);
} else {
retryRenderTask(
request,
task,
segment,
);
}
}
function retryRenderTask(
request: Request,
task: RenderTask,
segment: Segment,
): void {
if (segment.status !== PENDING) {
return;
}
segment.status = RENDERING;
switchContext(task.context);
let prevTaskInDEV = null;
if (__DEV__) {
prevTaskInDEV = currentTaskInDEV;
setCurrentTaskInDEV(task);
}
const childrenLength = segment.children.length;
const chunkLength = segment.chunks.length;
try {
retryNode(request, task);
pushSegmentFinale(
segment.chunks,
request.renderState,
segment.lastPushedText,
segment.textEmbedded,
);
task.abortSet.delete(task);
segment.status = COMPLETED;
finishedTask(request, task.blockedBoundary, segment);
} catch (thrownValue: mixed) {
resetHooksState();
segment.children.length = childrenLength;
segment.chunks.length = chunkLength;
const x =
thrownValue === SuspenseException
?
getSuspendedThenable()
: request.status === ABORTING
? request.fatalError
: thrownValue;
if (
enableHalt &&
request.status === ABORTING &&
request.trackedPostpones !== null
) {
const trackedPostpones = request.trackedPostpones;
const thrownInfo = getThrownInfo(task.componentStack);
task.abortSet.delete(task);
if (
enablePostpone &&
typeof x === 'object' &&
x !== null &&
x.$$typeof === REACT_POSTPONE_TYPE
) {
const postponeInstance: Postpone = (x: any);
logPostpone(
request,
postponeInstance.message,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
} else {
logRecoverableError(
request,
x,
thrownInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
}
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, task.blockedBoundary, segment);
return;
}
if (typeof x === 'object' && x !== null) {
if (typeof x.then === 'function') {
segment.status = PENDING;
task.thenableState = getThenableStateAfterSuspending();
const ping = task.ping;
(x: any).then(ping, ping);
return;
} else if (
enablePostpone &&
request.trackedPostpones !== null &&
x.$$typeof === REACT_POSTPONE_TYPE
) {
const trackedPostpones = request.trackedPostpones;
task.abortSet.delete(task);
const postponeInstance: Postpone = (x: any);
const postponeInfo = getThrownInfo(task.componentStack);
logPostpone(
request,
postponeInstance.message,
postponeInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
trackPostpone(request, trackedPostpones, task, segment);
finishedTask(request, task.blockedBoundary, segment);
return;
}
}
const errorInfo = getThrownInfo(task.componentStack);
task.abortSet.delete(task);
segment.status = ERRORED;
erroredTask(
request,
task.blockedBoundary,
x,
errorInfo,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
return;
} finally {
if (__DEV__) {
setCurrentTaskInDEV(prevTaskInDEV);
}
}
}
function retryReplayTask(request: Request, task: ReplayTask): void {
if (task.replay.pendingTasks === 0) {
return;
}
switchContext(task.context);
let prevTaskInDEV = null;
if (__DEV__) {
prevTaskInDEV = currentTaskInDEV;
setCurrentTaskInDEV(task);
}
try {
if (typeof task.replay.slots === 'number') {
const resumeSegmentID = task.replay.slots;
resumeNode(request, task, resumeSegmentID, task.node, task.childIndex);
} else {
retryNode(request, task);
}
if (task.replay.pendingTasks === 1 && task.replay.nodes.length > 0) {
throw new Error(
"Couldn't find all resumable slots by key/index during replaying. " +
"The tree doesn't match so React will fallback to client rendering.",
);
}
task.replay.pendingTasks--;
task.abortSet.delete(task);
finishedTask(request, task.blockedBoundary, null);
} catch (thrownValue) {
resetHooksState();
const x =
thrownValue === SuspenseException
?
getSuspendedThenable()
: thrownValue;
if (typeof x === 'object' && x !== null) {
if (typeof x.then === 'function') {
const ping = task.ping;
x.then(ping, ping);
task.thenableState = getThenableStateAfterSuspending();
return;
}
}
task.replay.pendingTasks--;
task.abortSet.delete(task);
const errorInfo = getThrownInfo(task.componentStack);
erroredReplay(
request,
task.blockedBoundary,
request.status === ABORTING ? request.fatalError : x,
errorInfo,
task.replay.nodes,
task.replay.slots,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
request.pendingRootTasks--;
if (request.pendingRootTasks === 0) {
completeShell(request);
}
request.allPendingTasks--;
if (request.allPendingTasks === 0) {
completeAll(request);
}
return;
} finally {
if (__DEV__) {
setCurrentTaskInDEV(prevTaskInDEV);
}
}
}
export function performWork(request: Request): void {
if (request.status === CLOSED || request.status === CLOSING) {
return;
}
const prevContext = getActiveContext();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = HooksDispatcher;
let prevAsyncDispatcher = null;
if (enableCache || __DEV__ || !disableStringRefs) {
prevAsyncDispatcher = ReactSharedInternals.A;
ReactSharedInternals.A = DefaultAsyncDispatcher;
}
const prevRequest = currentRequest;
currentRequest = request;
let prevGetCurrentStackImpl = null;
if (__DEV__) {
prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;
ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;
}
const prevResumableState = currentResumableState;
setCurrentResumableState(request.resumableState);
try {
const pingedTasks = request.pingedTasks;
let i;
for (i = 0; i < pingedTasks.length; i++) {
const task = pingedTasks[i];
retryTask(request, task);
}
pingedTasks.splice(0, i);
if (request.destination !== null) {
flushCompletedQueues(request, request.destination);
}
} catch (error) {
const errorInfo: ThrownInfo = {};
logRecoverableError(request, error, errorInfo, null);
fatalError(request, error, errorInfo, null);
} finally {
setCurrentResumableState(prevResumableState);
ReactSharedInternals.H = prevDispatcher;
if (enableCache) {
ReactSharedInternals.A = prevAsyncDispatcher;
}
if (__DEV__) {
ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;
}
if (prevDispatcher === HooksDispatcher) {
switchContext(prevContext);
}
currentRequest = prevRequest;
}
}
function flushPreamble(
request: Request,
destination: Destination,
rootSegment: Segment,
) {
const willFlushAllSegments =
request.allPendingTasks === 0 && request.trackedPostpones === null;
writePreamble(
destination,
request.resumableState,
request.renderState,
willFlushAllSegments,
);
}
function flushSubtree(
request: Request,
destination: Destination,
segment: Segment,
hoistableState: null | HoistableState,
): boolean {
segment.parentFlushed = true;
switch (segment.status) {
case PENDING: {
segment.id = request.nextSegmentId++;
}
case POSTPONED: {
const segmentID = segment.id;
segment.lastPushedText = false;
segment.textEmbedded = false;
return writePlaceholder(destination, request.renderState, segmentID);
}
case COMPLETED: {
segment.status = FLUSHED;
let r = true;
const chunks = segment.chunks;
let chunkIdx = 0;
const children = segment.children;
for (let childIdx = 0; childIdx < children.length; childIdx++) {
const nextChild = children[childIdx];
for (; chunkIdx < nextChild.index; chunkIdx++) {
writeChunk(destination, chunks[chunkIdx]);
}
r = flushSegment(request, destination, nextChild, hoistableState);
}
for (; chunkIdx < chunks.length - 1; chunkIdx++) {
writeChunk(destination, chunks[chunkIdx]);
}
if (chunkIdx < chunks.length) {
r = writeChunkAndReturn(destination, chunks[chunkIdx]);
}
return r;
}
default: {
throw new Error(
'Aborted, errored or already flushed boundaries should not be flushed again. This is a bug in React.',
);
}
}
}
function flushSegment(
request: Request,
destination: Destination,
segment: Segment,
hoistableState: null | HoistableState,
): boolean {
const boundary = segment.boundary;
if (boundary === null) {
return flushSubtree(request, destination, segment, hoistableState);
}
boundary.parentFlushed = true;
if (boundary.status === CLIENT_RENDERED) {
if (__DEV__) {
writeStartClientRenderedSuspenseBoundary(
destination,
request.renderState,
boundary.errorDigest,
boundary.errorMessage,
boundary.errorStack,
boundary.errorComponentStack,
);
} else {
writeStartClientRenderedSuspenseBoundary(
destination,
request.renderState,
boundary.errorDigest,
null,
null,
null,
);
}
flushSubtree(request, destination, segment, hoistableState);
return writeEndClientRenderedSuspenseBoundary(
destination,
request.renderState,
);
} else if (boundary.status !== COMPLETED) {
if (boundary.status === PENDING) {
boundary.rootSegmentID = request.nextSegmentId++;
}
if (boundary.completedSegments.length > 0) {
request.partialBoundaries.push(boundary);
}
const id = boundary.rootSegmentID;
writeStartPendingSuspenseBoundary(destination, request.renderState, id);
if (hoistableState) {
hoistHoistables(hoistableState, boundary.fallbackState);
}
flushSubtree(request, destination, segment, hoistableState);
return writeEndPendingSuspenseBoundary(destination, request.renderState);
} else if (boundary.byteSize > request.progressiveChunkSize) {
boundary.rootSegmentID = request.nextSegmentId++;
request.completedBoundaries.push(boundary);
writeStartPendingSuspenseBoundary(
destination,
request.renderState,
boundary.rootSegmentID,
);
flushSubtree(request, destination, segment, hoistableState);
return writeEndPendingSuspenseBoundary(destination, request.renderState);
} else {
if (hoistableState) {
hoistHoistables(hoistableState, boundary.contentState);
}
writeStartCompletedSuspenseBoundary(destination, request.renderState);
const completedSegments = boundary.completedSegments;
if (completedSegments.length !== 1) {
throw new Error(
'A previously unvisited boundary must have exactly one root segment. This is a bug in React.',
);
}
const contentSegment = completedSegments[0];
flushSegment(request, destination, contentSegment, hoistableState);
return writeEndCompletedSuspenseBoundary(destination, request.renderState);
}
}
function flushClientRenderedBoundary(
request: Request,
destination: Destination,
boundary: SuspenseBoundary,
): boolean {
if (__DEV__) {
return writeClientRenderBoundaryInstruction(
destination,
request.resumableState,
request.renderState,
boundary.rootSegmentID,
boundary.errorDigest,
boundary.errorMessage,
boundary.errorStack,
boundary.errorComponentStack,
);
} else {
return writeClientRenderBoundaryInstruction(
destination,
request.resumableState,
request.renderState,
boundary.rootSegmentID,
boundary.errorDigest,
null,
null,
null,
);
}
}
function flushSegmentContainer(
request: Request,
destination: Destination,
segment: Segment,
hoistableState: HoistableState,
): boolean {
writeStartSegment(
destination,
request.renderState,
segment.parentFormatContext,
segment.id,
);
flushSegment(request, destination, segment, hoistableState);
return writeEndSegment(destination, segment.parentFormatContext);
}
function flushCompletedBoundary(
request: Request,
destination: Destination,
boundary: SuspenseBoundary,
): boolean {
const completedSegments = boundary.completedSegments;
let i = 0;
for (; i < completedSegments.length; i++) {
const segment = completedSegments[i];
flushPartiallyCompletedSegment(request, destination, boundary, segment);
}
completedSegments.length = 0;
writeHoistablesForBoundary(
destination,
boundary.contentState,
request.renderState,
);
return writeCompletedBoundaryInstruction(
destination,
request.resumableState,
request.renderState,
boundary.rootSegmentID,
boundary.contentState,
);
}
function flushPartialBoundary(
request: Request,
destination: Destination,
boundary: SuspenseBoundary,
): boolean {
const completedSegments = boundary.completedSegments;
let i = 0;
for (; i < completedSegments.length; i++) {
const segment = completedSegments[i];
if (
!flushPartiallyCompletedSegment(request, destination, boundary, segment)
) {
i++;
completedSegments.splice(0, i);
return false;
}
}
completedSegments.splice(0, i);
return writeHoistablesForBoundary(
destination,
boundary.contentState,
request.renderState,
);
}
function flushPartiallyCompletedSegment(
request: Request,
destination: Destination,
boundary: SuspenseBoundary,
segment: Segment,
): boolean {
if (segment.status === FLUSHED) {
return true;
}
const hoistableState = boundary.contentState;
const segmentID = segment.id;
if (segmentID === -1) {
const rootSegmentID = (segment.id = boundary.rootSegmentID);
if (rootSegmentID === -1) {
throw new Error(
'A root segment ID must have been assigned by now. This is a bug in React.',
);
}
return flushSegmentContainer(request, destination, segment, hoistableState);
} else if (segmentID === boundary.rootSegmentID) {
return flushSegmentContainer(request, destination, segment, hoistableState);
} else {
flushSegmentContainer(request, destination, segment, hoistableState);
return writeCompletedSegmentInstruction(
destination,
request.resumableState,
request.renderState,
segmentID,
);
}
}
function flushCompletedQueues(
request: Request,
destination: Destination,
): void {
beginWriting(destination);
try {
if (request.pendingRootTasks > 0) {
return;
}
let i;
const completedRootSegment = request.completedRootSegment;
if (completedRootSegment !== null) {
if (completedRootSegment.status === POSTPONED) {
return;
}
flushPreamble(request, destination, completedRootSegment);
flushSegment(request, destination, completedRootSegment, null);
request.completedRootSegment = null;
writeCompletedRoot(destination, request.renderState);
}
writeHoistables(destination, request.resumableState, request.renderState);
const clientRenderedBoundaries = request.clientRenderedBoundaries;
for (i = 0; i < clientRenderedBoundaries.length; i++) {
const boundary = clientRenderedBoundaries[i];
if (!flushClientRenderedBoundary(request, destination, boundary)) {
request.destination = null;
i++;
clientRenderedBoundaries.splice(0, i);
return;
}
}
clientRenderedBoundaries.splice(0, i);
const completedBoundaries = request.completedBoundaries;
for (i = 0; i < completedBoundaries.length; i++) {
const boundary = completedBoundaries[i];
if (!flushCompletedBoundary(request, destination, boundary)) {
request.destination = null;
i++;
completedBoundaries.splice(0, i);
return;
}
}
completedBoundaries.splice(0, i);
completeWriting(destination);
beginWriting(destination);
const partialBoundaries = request.partialBoundaries;
for (i = 0; i < partialBoundaries.length; i++) {
const boundary = partialBoundaries[i];
if (!flushPartialBoundary(request, destination, boundary)) {
request.destination = null;
i++;
partialBoundaries.splice(0, i);
return;
}
}
partialBoundaries.splice(0, i);
const largeBoundaries = request.completedBoundaries;
for (i = 0; i < largeBoundaries.length; i++) {
const boundary = largeBoundaries[i];
if (!flushCompletedBoundary(request, destination, boundary)) {
request.destination = null;
i++;
largeBoundaries.splice(0, i);
return;
}
}
largeBoundaries.splice(0, i);
} finally {
if (
request.allPendingTasks === 0 &&
request.pingedTasks.length === 0 &&
request.clientRenderedBoundaries.length === 0 &&
request.completedBoundaries.length === 0
) {
request.flushScheduled = false;
if (!enablePostpone || request.trackedPostpones === null) {
writePostamble(destination, request.resumableState);
}
completeWriting(destination);
flushBuffered(destination);
if (__DEV__) {
if (request.abortableTasks.size !== 0) {
console.error(
'There was still abortable task at the root when we closed. This is a bug in React.',
);
}
}
request.status = CLOSED;
close(destination);
stopFlowing(request);
} else {
completeWriting(destination);
flushBuffered(destination);
}
}
}
export function startWork(request: Request): void {
request.flushScheduled = request.destination !== null;
if (supportsRequestStorage) {
scheduleMicrotask(() => requestStorage.run(request, performWork, request));
} else {
scheduleMicrotask(() => performWork(request));
}
scheduleWork(() => {
if (request.status === OPENING) {
request.status = OPEN;
}
if (request.trackedPostpones === null) {
if (supportsRequestStorage) {
requestStorage.run(
request,
enqueueEarlyPreloadsAfterInitialWork,
request,
);
} else {
enqueueEarlyPreloadsAfterInitialWork(request);
}
}
});
}
function enqueueEarlyPreloadsAfterInitialWork(request: Request) {
const shellComplete = request.pendingRootTasks === 0;
safelyEmitEarlyPreloads(request, shellComplete);
}
function enqueueFlush(request: Request): void {
if (
request.flushScheduled === false &&
request.pingedTasks.length === 0 &&
request.destination !== null
) {
request.flushScheduled = true;
scheduleWork(() => {
const destination = request.destination;
if (destination) {
flushCompletedQueues(request, destination);
} else {
request.flushScheduled = false;
}
});
}
}
export function prepareForStartFlowingIfBeforeAllReady(request: Request) {
const shellComplete =
request.trackedPostpones === null
?
request.pendingRootTasks === 0
:
request.completedRootSegment === null
? request.pendingRootTasks === 0
: request.completedRootSegment.status !== POSTPONED;
safelyEmitEarlyPreloads(request, shellComplete);
}
export function startFlowing(request: Request, destination: Destination): void {
if (request.status === CLOSING) {
request.status = CLOSED;
closeWithError(destination, request.fatalError);
return;
}
if (request.status === CLOSED) {
return;
}
if (request.destination !== null) {
return;
}
request.destination = destination;
try {
flushCompletedQueues(request, destination);
} catch (error) {
const errorInfo: ThrownInfo = {};
logRecoverableError(request, error, errorInfo, null);
fatalError(request, error, errorInfo, null);
}
}
export function stopFlowing(request: Request): void {
request.destination = null;
}
export function abort(request: Request, reason: mixed): void {
if (request.status === OPEN || request.status === OPENING) {
request.status = ABORTING;
}
try {
const abortableTasks = request.abortableTasks;
if (abortableTasks.size > 0) {
const error =
reason === undefined
? new Error('The render was aborted by the server without a reason.')
: typeof reason === 'object' &&
reason !== null &&
typeof reason.then === 'function'
? new Error('The render was aborted by the server with a promise.')
: reason;
request.fatalError = error;
abortableTasks.forEach(task => abortTask(task, request, error));
abortableTasks.clear();
}
if (request.destination !== null) {
flushCompletedQueues(request, request.destination);
}
} catch (error) {
const errorInfo: ThrownInfo = {};
logRecoverableError(request, error, errorInfo, null);
fatalError(request, error, errorInfo, null);
}
}
export function flushResources(request: Request): void {
enqueueFlush(request);
}
export function getFormState(
request: Request,
): ReactFormState<any, any> | null {
return request.formState;
}
export function getResumableState(request: Request): ResumableState {
return request.resumableState;
}
export function getRenderState(request: Request): RenderState {
return request.renderState;
}
function addToReplayParent(
node: ReplayNode,
parentKeyPath: Root | KeyNode,
trackedPostpones: PostponedHoles,
): void {
if (parentKeyPath === null) {
trackedPostpones.rootNodes.push(node);
} else {
const workingMap = trackedPostpones.workingMap;
let parentNode = workingMap.get(parentKeyPath);
if (parentNode === undefined) {
parentNode = ([
parentKeyPath[1],
parentKeyPath[2],
([]: Array<ReplayNode>),
null,
]: ReplayNode);
workingMap.set(parentKeyPath, parentNode);
addToReplayParent(parentNode, parentKeyPath[0], trackedPostpones);
}
parentNode[2].push(node);
}
}
export type PostponedState = {
nextSegmentId: number,
rootFormatContext: FormatContext,
progressiveChunkSize: number,
resumableState: ResumableState,
replayNodes: Array<ReplayNode>,
replaySlots: ResumeSlots,
};
export function getPostponedState(request: Request): null | PostponedState {
const trackedPostpones = request.trackedPostpones;
if (
trackedPostpones === null ||
(trackedPostpones.rootNodes.length === 0 &&
trackedPostpones.rootSlots === null)
) {
request.trackedPostpones = null;
return null;
}
if (
request.completedRootSegment !== null &&
request.completedRootSegment.status === POSTPONED
) {
resetResumableState(request.resumableState, request.renderState);
} else {
completeResumableState(request.resumableState);
}
return {
nextSegmentId: request.nextSegmentId,
rootFormatContext: request.rootFormatContext,
progressiveChunkSize: request.progressiveChunkSize,
resumableState: request.resumableState,
replayNodes: trackedPostpones.rootNodes,
replaySlots: trackedPostpones.rootSlots,
};
}