diff --git a/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js b/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js
index 73887c16d8..1e64b3e15f 100644
--- a/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js
+++ b/packages/react-devtools-shared/src/backend/DevToolsFiberComponentStack.js
@@ -47,6 +47,7 @@ export function describeFiber(
case HostComponent:
return describeBuiltInComponentFrame(workInProgress.type);
case LazyComponent:
+ // TODO: When we support Thenables as component types we should rename this.
return describeBuiltInComponentFrame('Lazy');
case SuspenseComponent:
return describeBuiltInComponentFrame('Suspense');
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 62e0a17af0..d534df76a5 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -700,17 +700,39 @@ describe('ReactDOMFizzServer', () => {
it('should client render a boundary if a lazy component rejects', async () => {
let rejectComponent;
+ const promise = new Promise((resolve, reject) => {
+ rejectComponent = reject;
+ });
const LazyComponent = React.lazy(() => {
- return new Promise((resolve, reject) => {
- rejectComponent = reject;
- });
+ return promise;
+ });
+
+ const LazyLazy = React.lazy(async () => {
+ return {
+ default: LazyComponent,
+ };
+ });
+
+ function Wrapper({children}) {
+ return children;
+ }
+ const LazyWrapper = React.lazy(() => {
+ return {
+ then(callback) {
+ callback({
+ default: Wrapper,
+ });
+ },
+ };
});
function App({isClient}) {
return (
}>
- {isClient ? : }
+
+ {isClient ? : }
+
);
@@ -744,6 +766,7 @@ describe('ReactDOMFizzServer', () => {
});
pipe(writable);
});
+
expect(loggedErrors).toEqual([]);
expect(bootstrapped).toBe(true);
@@ -772,7 +795,7 @@ describe('ReactDOMFizzServer', () => {
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
- componentStack(['Lazy', 'Suspense', 'div', 'App']),
+ componentStack(['Lazy', 'Wrapper', 'Suspense', 'div', 'App']),
],
],
[
@@ -852,13 +875,9 @@ describe('ReactDOMFizzServer', () => {
}
await act(() => {
- const {pipe} = renderToPipeableStream(
- ,
-
- {
- onError,
- },
- );
+ const {pipe} = renderToPipeableStream(, {
+ onError,
+ });
pipe(writable);
});
expect(loggedErrors).toEqual([]);
@@ -896,7 +915,7 @@ describe('ReactDOMFizzServer', () => {
'Switched to client rendering because the server rendering errored:\n\n' +
theError.message,
expectedDigest,
- componentStack(['Lazy', 'Suspense', 'div', 'App']),
+ componentStack(['Suspense', 'div', 'App']),
],
],
[
@@ -1395,13 +1414,13 @@ describe('ReactDOMFizzServer', () => {
'The render was aborted by the server without a reason.',
expectedDigest,
// We get the stack of the task when it was aborted which is why we see `h1`
- componentStack(['h1', 'Suspense', 'div', 'App']),
+ componentStack(['AsyncText', 'h1', 'Suspense', 'div', 'App']),
],
[
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'The render was aborted by the server without a reason.',
expectedDigest,
- componentStack(['Suspense', 'main', 'div', 'App']),
+ componentStack(['AsyncText', 'Suspense', 'main', 'div', 'App']),
],
],
[
@@ -3523,13 +3542,13 @@ describe('ReactDOMFizzServer', () => {
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'foobar',
'a digest',
- componentStack(['Suspense', 'p', 'div', 'App']),
+ componentStack(['AsyncText', 'Suspense', 'p', 'div', 'App']),
],
[
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'foobar',
'a digest',
- componentStack(['Suspense', 'span', 'div', 'App']),
+ componentStack(['AsyncText', 'Suspense', 'span', 'div', 'App']),
],
],
[
@@ -3606,13 +3625,13 @@ describe('ReactDOMFizzServer', () => {
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'uh oh',
'a digest',
- componentStack(['Suspense', 'p', 'div', 'App']),
+ componentStack(['AsyncText', 'Suspense', 'p', 'div', 'App']),
],
[
'Switched to client rendering because the server rendering aborted due to:\n\n' +
'uh oh',
'a digest',
- componentStack(['Suspense', 'span', 'div', 'App']),
+ componentStack(['AsyncText', 'Suspense', 'span', 'div', 'App']),
],
],
[
diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
index eb41a627b7..1e93c2420b 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
@@ -585,7 +585,7 @@ describe('ReactDOMFizzServerNode', () => {
let isComplete = false;
let rendered = false;
const promise = new Promise(r => (resolve = r));
- function Wait() {
+ function Wait({prop}) {
if (!hasLoaded) {
throw promise;
}
diff --git a/packages/react-html/src/__tests__/ReactHTMLServer-test.js b/packages/react-html/src/__tests__/ReactHTMLServer-test.js
index 01c5873b56..98678083d5 100644
--- a/packages/react-html/src/__tests__/ReactHTMLServer-test.js
+++ b/packages/react-html/src/__tests__/ReactHTMLServer-test.js
@@ -250,9 +250,7 @@ if (!__EXPERIMENTAL__) {
'\n in Bar (at **)' +
'\n in Foo (at **)' +
'\n in div (at **)'
- : '\n in Lazy (at **)' +
- '\n in div (at **)' +
- '\n in div (at **)',
+ : '\n in div (at **)' + '\n in div (at **)',
);
expect(normalizeCodeLocInfo(caughtErrors[0].ownerStack)).toBe(
__DEV__ && gate(flags => flags.enableOwnerStacks)
diff --git a/packages/react-reconciler/src/ReactFiberComponentStack.js b/packages/react-reconciler/src/ReactFiberComponentStack.js
index 18f65530ea..a06411acad 100644
--- a/packages/react-reconciler/src/ReactFiberComponentStack.js
+++ b/packages/react-reconciler/src/ReactFiberComponentStack.js
@@ -39,6 +39,7 @@ function describeFiber(fiber: Fiber): string {
case HostComponent:
return describeBuiltInComponentFrame(fiber.type);
case LazyComponent:
+ // TODO: When we support Thenables as component types we should rename this.
return describeBuiltInComponentFrame('Lazy');
case SuspenseComponent:
return describeBuiltInComponentFrame('Suspense');
diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
index 4997184078..9d4a123ac3 100644
--- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
+++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMEdge-test.js
@@ -930,4 +930,74 @@ describe('ReactFlightDOMEdge', () => {
'\n in Bar (at **)' + '\n in Foo (at **)',
);
});
+
+ it('supports server components in ssr component stacks', async () => {
+ let reject;
+ const promise = new Promise((_, r) => (reject = r));
+ async function Erroring() {
+ await promise;
+ return 'should not render';
+ }
+
+ const model = {
+ root: ReactServer.createElement(Erroring),
+ };
+
+ const stream = ReactServerDOMServer.renderToReadableStream(
+ model,
+ webpackMap,
+ {
+ onError() {},
+ },
+ );
+
+ const rootModel = await ReactServerDOMClient.createFromReadableStream(
+ stream,
+ {
+ ssrManifest: {
+ moduleMap: null,
+ moduleLoading: null,
+ },
+ },
+ );
+
+ const errors = [];
+ const result = ReactDOMServer.renderToReadableStream(
+ {rootModel.root}
,
+ {
+ onError(error, {componentStack}) {
+ errors.push({
+ error,
+ componentStack: normalizeCodeLocInfo(componentStack),
+ });
+ },
+ },
+ );
+
+ const theError = new Error('my error');
+ reject(theError);
+
+ const expectedMessage = __DEV__
+ ? 'my error'
+ : 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.';
+
+ try {
+ await result;
+ } catch (x) {
+ expect(x).toEqual(
+ expect.objectContaining({
+ message: expectedMessage,
+ }),
+ );
+ }
+
+ expect(errors).toEqual([
+ {
+ error: expect.objectContaining({
+ message: expectedMessage,
+ }),
+ componentStack: (__DEV__ ? '\n in Erroring' : '') + '\n in div',
+ },
+ ]);
+ });
});
diff --git a/packages/react-server/src/ReactFizzComponentStack.js b/packages/react-server/src/ReactFizzComponentStack.js
index 6e3a31b284..b280983359 100644
--- a/packages/react-server/src/ReactFizzComponentStack.js
+++ b/packages/react-server/src/ReactFizzComponentStack.js
@@ -8,52 +8,96 @@
*/
import type {ReactComponentInfo} from 'shared/ReactTypes';
+import type {LazyComponent} from 'react/src/ReactLazy';
import {
describeBuiltInComponentFrame,
describeFunctionComponentFrame,
describeClassComponentFrame,
+ describeDebugInfoFrame,
} from 'shared/ReactComponentStackFrame';
+import {
+ REACT_FORWARD_REF_TYPE,
+ REACT_MEMO_TYPE,
+ REACT_LAZY_TYPE,
+ REACT_SUSPENSE_LIST_TYPE,
+ REACT_SUSPENSE_TYPE,
+} from 'shared/ReactSymbols';
+
import {enableOwnerStacks} from 'shared/ReactFeatureFlags';
import {formatOwnerStack} from './ReactFizzOwnerStack';
-// DEV-only reverse linked list representing the current component stack
-type BuiltInComponentStackNode = {
- tag: 0,
+export type ComponentStackNode = {
parent: null | ComponentStackNode,
- type: string,
+ type:
+ | symbol
+ | string
+ | Function
+ | LazyComponent
+ | ReactComponentInfo,
owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
stack?: null | string | Error, // DEV only
};
-type FunctionComponentStackNode = {
- tag: 1,
- parent: null | ComponentStackNode,
- type: Function,
- owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack?: null | string | Error, // DEV only
-};
-type ClassComponentStackNode = {
- tag: 2,
- parent: null | ComponentStackNode,
- type: Function,
- owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack?: null | string | Error, // DEV only
-};
-type ServerComponentStackNode = {
- // DEV only
- tag: 3,
- parent: null | ComponentStackNode,
- type: string, // name + env
- owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack?: null | string | Error, // DEV only
-};
-export type ComponentStackNode =
- | BuiltInComponentStackNode
- | FunctionComponentStackNode
- | ClassComponentStackNode
- | ServerComponentStackNode;
+
+function shouldConstruct(Component: any) {
+ return Component.prototype && Component.prototype.isReactComponent;
+}
+
+function describeComponentStackByType(
+ type:
+ | symbol
+ | string
+ | Function
+ | LazyComponent
+ | ReactComponentInfo,
+): string {
+ if (typeof type === 'string') {
+ return describeBuiltInComponentFrame(type);
+ }
+ if (typeof type === 'function') {
+ if (shouldConstruct(type)) {
+ return describeClassComponentFrame(type);
+ } else {
+ return describeFunctionComponentFrame(type);
+ }
+ }
+ if (typeof type === 'object' && type !== null) {
+ switch (type.$$typeof) {
+ case REACT_FORWARD_REF_TYPE: {
+ return describeFunctionComponentFrame((type: any).render);
+ }
+ case REACT_MEMO_TYPE: {
+ return describeFunctionComponentFrame((type: any).type);
+ }
+ case REACT_LAZY_TYPE: {
+ const lazyComponent: LazyComponent = (type: any);
+ const payload = lazyComponent._payload;
+ const init = lazyComponent._init;
+ try {
+ type = init(payload);
+ } catch (x) {
+ // TODO: When we support Thenables as component types we should rename this.
+ return describeBuiltInComponentFrame('Lazy');
+ }
+ return describeComponentStackByType(type);
+ }
+ }
+ if (typeof type.name === 'string') {
+ return describeDebugInfoFrame(type.name, type.env);
+ }
+ }
+ switch (type) {
+ case REACT_SUSPENSE_LIST_TYPE: {
+ return describeBuiltInComponentFrame('SuspenseList');
+ }
+ case REACT_SUSPENSE_TYPE: {
+ return describeBuiltInComponentFrame('Suspense');
+ }
+ }
+ return '';
+}
export function getStackByComponentStackNode(
componentStack: ComponentStackNode,
@@ -62,22 +106,7 @@ export function getStackByComponentStackNode(
let info = '';
let node: ComponentStackNode = componentStack;
do {
- switch (node.tag) {
- case 0:
- info += describeBuiltInComponentFrame(node.type);
- break;
- case 1:
- info += describeFunctionComponentFrame(node.type);
- break;
- case 2:
- info += describeClassComponentFrame(node.type);
- break;
- case 3:
- if (__DEV__) {
- info += describeBuiltInComponentFrame(node.type);
- break;
- }
- }
+ info += describeComponentStackByType(node.type);
// $FlowFixMe[incompatible-type] we bail out when we get a null
node = node.parent;
} while (node);
@@ -110,59 +139,41 @@ export function getOwnerStackByComponentStackNodeInDev(
// add one extra frame just to describe the "current" built-in component by name.
// Similarly, if there is no owner at all, then there's no stack frame so we add the name
// of the root component to the stack to know which component is currently executing.
- switch (componentStack.tag) {
- case 0:
- info += describeBuiltInComponentFrame(componentStack.type);
- break;
- case 1:
- case 2:
- if (!componentStack.owner) {
- // Only if we have no other data about the callsite do we add
- // the component name as the single stack frame.
- info += describeFunctionComponentFrameWithoutLineNumber(
- componentStack.type,
- );
- }
- break;
- case 3:
- if (!componentStack.owner) {
- info += describeBuiltInComponentFrame(componentStack.type);
- }
- break;
+ if (typeof componentStack.type === 'string') {
+ info += describeBuiltInComponentFrame(componentStack.type);
+ } else if (typeof componentStack.type === 'function') {
+ if (!componentStack.owner) {
+ // Only if we have no other data about the callsite do we add
+ // the component name as the single stack frame.
+ info += describeFunctionComponentFrameWithoutLineNumber(
+ componentStack.type,
+ );
+ }
+ } else {
+ if (!componentStack.owner) {
+ info += describeComponentStackByType(componentStack.type);
+ }
}
let owner: void | null | ComponentStackNode | ReactComponentInfo =
componentStack;
while (owner) {
- if (typeof owner.tag === 'number') {
- const node: ComponentStackNode = (owner: any);
- owner = node.owner;
- let debugStack = node.stack;
- // If we don't actually print the stack if there is no owner of this JSX element.
- // In a real app it's typically not useful since the root app is always controlled
- // by the framework. These also tend to have noisy stacks because they're not rooted
- // in a React render but in some imperative bootstrapping code. It could be useful
- // if the element was created in module scope. E.g. hoisted. We could add a a single
- // stack frame for context for example but it doesn't say much if that's a wrapper.
- if (owner && debugStack) {
- if (typeof debugStack !== 'string') {
- // Stash the formatted stack so that we can avoid redoing the filtering.
- node.stack = debugStack = formatOwnerStack(debugStack);
- }
- if (debugStack !== '') {
- info += '\n' + debugStack;
- }
- }
- } else if (typeof owner.stack === 'string') {
- // Server Component
- const ownerStack: string = owner.stack;
- owner = owner.owner;
- if (owner && ownerStack !== '') {
- info += '\n' + ownerStack;
- }
- } else {
- break;
+ let debugStack: void | null | string | Error = owner.stack;
+ if (typeof debugStack !== 'string' && debugStack != null) {
+ // Stash the formatted stack so that we can avoid redoing the filtering.
+ // $FlowFixMe[cannot-write]: This has been refined to a ComponentStackNode.
+ owner.stack = debugStack = formatOwnerStack(debugStack);
+ }
+ owner = owner.owner;
+ // If we don't actually print the stack if there is no owner of this JSX element.
+ // In a real app it's typically not useful since the root app is always controlled
+ // by the framework. These also tend to have noisy stacks because they're not rooted
+ // in a React render but in some imperative bootstrapping code. It could be useful
+ // if the element was created in module scope. E.g. hoisted. We could add a a single
+ // stack frame for context for example but it doesn't say much if that's a wrapper.
+ if (owner && debugStack) {
+ info += '\n' + debugStack;
}
}
return info;
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index cf44e055ca..afdc9efbb2 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -472,6 +472,7 @@ function RequestInstance(
emptyContextObject,
null,
);
+ pushComponentStack(rootTask);
pingedTasks.push(rootTask);
}
@@ -615,6 +616,7 @@ export function resumeRequest(
emptyContextObject,
null,
);
+ pushComponentStack(rootTask);
pingedTasks.push(rootTask);
return request;
}
@@ -642,6 +644,7 @@ export function resumeRequest(
emptyContextObject,
null,
);
+ pushComponentStack(rootTask);
pingedTasks.push(rootTask);
return request;
}
@@ -837,69 +840,6 @@ function getStackFromNode(stackNode: ComponentStackNode): string {
return getStackByComponentStackNode(stackNode);
}
-function createBuiltInComponentStack(
- task: Task,
- type: string,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
-): ComponentStackNode {
- if (__DEV__) {
- return {
- tag: 0,
- parent: task.componentStack,
- type,
- owner,
- stack,
- };
- }
- return {
- tag: 0,
- parent: task.componentStack,
- type,
- };
-}
-function createFunctionComponentStack(
- task: Task,
- type: Function,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
-): ComponentStackNode {
- if (__DEV__) {
- return {
- tag: 1,
- parent: task.componentStack,
- type,
- owner,
- stack,
- };
- }
- return {
- tag: 1,
- parent: task.componentStack,
- type,
- };
-}
-function createClassComponentStack(
- task: Task,
- type: Function,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
-): ComponentStackNode {
- if (__DEV__) {
- return {
- tag: 2,
- parent: task.componentStack,
- type,
- owner,
- stack,
- };
- }
- return {
- tag: 2,
- parent: task.componentStack,
- type,
- };
-}
function pushServerComponentStack(
task: Task,
debugInfo: void | null | ReactDebugInfo,
@@ -921,15 +861,9 @@ function pushServerComponentStack(
if (enableOwnerStacks && componentInfo.stack === undefined) {
continue;
}
- let name = componentInfo.name;
- const env = componentInfo.env;
- if (env) {
- name += ' [' + env + ']';
- }
task.componentStack = {
- tag: 3,
parent: task.componentStack,
- type: name,
+ type: componentInfo,
owner: componentInfo.owner,
stack: componentInfo.stack,
};
@@ -940,19 +874,70 @@ function pushServerComponentStack(
}
}
+function pushComponentStack(task: Task): void {
+ const node = task.node;
+ // Create the Component Stack frame for the element we're about to try.
+ // It's unfortunate that we need to do this refinement twice. Once for
+ // the stack frame and then once again while actually
+ 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 = (node: any);
+ pushServerComponentStack(task, lazyNode._debugInfo);
+ }
+ break;
+ }
+ default: {
+ if (__DEV__) {
+ const maybeUsable: Object = node;
+ if (typeof maybeUsable.then === 'function') {
+ const thenable: Thenable = (maybeUsable: any);
+ pushServerComponentStack(task, thenable._debugInfo);
+ }
+ }
+ }
+ }
+ }
+}
+
function createComponentStackFromType(
- task: Task,
- type: Function | string,
+ parent: null | ComponentStackNode,
+ type: Function | string | symbol,
owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
stack: null | Error, // DEV only
): ComponentStackNode {
- if (typeof type === 'string') {
- return createBuiltInComponentStack(task, type, owner, stack);
+ if (__DEV__) {
+ return {
+ parent,
+ type,
+ owner,
+ stack,
+ };
}
- if (shouldConstruct(type)) {
- return createClassComponentStack(task, type, owner, stack);
- }
- return createFunctionComponentStack(task, type, owner, stack);
+ return {
+ parent,
+ type,
+ };
}
type ThrownInfo = {
@@ -1088,8 +1073,6 @@ function renderSuspenseBoundary(
someTask: Task,
keyPath: KeyNode,
props: Object,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
if (someTask.replay !== null) {
// If we're replaying through this pass, it means we're replaying through
@@ -1108,12 +1091,6 @@ function renderSuspenseBoundary(
// $FlowFixMe: Refined.
const task: RenderTask = someTask;
- const previousComponentStack = task.componentStack;
- // If we end up creating the fallback task we need it to have the correct stack which is
- // the stack for the boundary itself. We stash it here so we can use it if needed later
- const suspenseComponentStack = (task.componentStack =
- createBuiltInComponentStack(task, 'Suspense', owner, stack));
-
const prevKeyPath = task.keyPath;
const parentBoundary = task.blockedBoundary;
const parentHoistableState = task.hoistableState;
@@ -1189,9 +1166,6 @@ function renderSuspenseBoundary(
// Therefore we won't need the fallback. We early return so that we don't have to create
// the fallback.
newBoundary.status = COMPLETED;
-
- // We are returning early so we need to restore the
- task.componentStack = previousComponentStack;
return;
}
} catch (error: mixed) {
@@ -1234,7 +1208,6 @@ function renderSuspenseBoundary(
task.hoistableState = parentHoistableState;
task.blockedSegment = parentSegment;
task.keyPath = prevKeyPath;
- task.componentStack = previousComponentStack;
}
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
@@ -1274,13 +1247,12 @@ function renderSuspenseBoundary(
task.formatContext,
task.context,
task.treeContext,
- // This stack should be the Suspense boundary stack because while the fallback is actually a child segment
- // of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself
- suspenseComponentStack,
+ task.componentStack,
true,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
+ pushComponentStack(suspendedFallbackTask);
// TODO: This should be queued at a separate lower priority queue so that we only work
// on preparing fallbacks if we don't have any more main content to task on.
request.pingedTasks.push(suspendedFallbackTask);
@@ -1296,15 +1268,7 @@ function replaySuspenseBoundary(
childSlots: ResumeSlots,
fallbackNodes: Array,
fallbackSlots: ResumeSlots,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
- const previousComponentStack = task.componentStack;
- // If we end up creating the fallback task we need it to have the correct stack which is
- // the stack for the boundary itself. We stash it here so we can use it if needed later
- const suspenseComponentStack = (task.componentStack =
- createBuiltInComponentStack(task, 'Suspense', owner, stack));
-
const prevKeyPath = task.keyPath;
const previousReplaySet: ReplaySet = task.replay;
@@ -1400,7 +1364,6 @@ function replaySuspenseBoundary(
task.hoistableState = parentHoistableState;
task.replay = previousReplaySet;
task.keyPath = prevKeyPath;
- task.componentStack = previousComponentStack;
}
const fallbackKeyPath = [keyPath[0], 'Suspense Fallback', keyPath[2]];
@@ -1425,13 +1388,12 @@ function replaySuspenseBoundary(
task.formatContext,
task.context,
task.treeContext,
- // This stack should be the Suspense boundary stack because while the fallback is actually a child segment
- // of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself
- suspenseComponentStack,
+ task.componentStack,
true,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
);
+ pushComponentStack(suspendedFallbackTask);
// TODO: This should be queued at a separate lower priority queue so that we only work
// on preparing fallbacks if we don't have any more main content to task on.
request.pingedTasks.push(suspendedFallbackTask);
@@ -1442,17 +1404,7 @@ function renderBackupSuspenseBoundary(
task: Task,
keyPath: KeyNode,
props: Object,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
) {
- const previousComponentStack = task.componentStack;
- task.componentStack = createBuiltInComponentStack(
- task,
- 'Suspense',
- owner,
- stack,
- );
-
const content = props.children;
const segment = task.blockedSegment;
const prevKeyPath = task.keyPath;
@@ -1467,7 +1419,6 @@ function renderBackupSuspenseBoundary(
pushEndCompletedSuspenseBoundary(segment.chunks);
}
task.keyPath = prevKeyPath;
- task.componentStack = previousComponentStack;
}
function renderHostElement(
@@ -1476,11 +1427,7 @@ function renderHostElement(
keyPath: KeyNode,
type: string,
props: Object,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
- const previousComponentStack = task.componentStack;
- task.componentStack = createBuiltInComponentStack(task, type, owner, stack);
const segment = task.blockedSegment;
if (segment === null) {
// Replay
@@ -1534,7 +1481,6 @@ function renderHostElement(
);
segment.lastPushedText = false;
}
- task.componentStack = previousComponentStack;
}
function shouldConstruct(Component: any) {
@@ -1670,17 +1616,8 @@ function renderClassComponent(
keyPath: KeyNode,
Component: any,
props: any,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
const resolvedProps = resolveClassComponentProps(Component, props);
- const previousComponentStack = task.componentStack;
- task.componentStack = createClassComponentStack(
- task,
- Component,
- owner,
- stack,
- );
const maskedContext = !disableLegacyContext
? getMaskedContext(Component, task.legacyContext)
: undefined;
@@ -1698,7 +1635,6 @@ function renderClassComponent(
Component,
resolvedProps,
);
- task.componentStack = previousComponentStack;
}
const didWarnAboutBadClass: {[string]: boolean} = {};
@@ -1715,21 +1651,11 @@ function renderFunctionComponent(
keyPath: KeyNode,
Component: any,
props: any,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
let legacyContext;
if (!disableLegacyContext) {
legacyContext = getMaskedContext(Component, task.legacyContext);
}
- const previousComponentStack = task.componentStack;
- task.componentStack = createFunctionComponentStack(
- task,
- Component,
- owner,
- stack,
- );
-
if (__DEV__) {
if (
Component.prototype &&
@@ -1782,7 +1708,6 @@ function renderFunctionComponent(
actionStateCount,
actionStateMatchingIndex,
);
- task.componentStack = previousComponentStack;
}
function finishFunctionComponent(
@@ -1931,17 +1856,7 @@ function renderForwardRef(
type: any,
props: Object,
ref: any,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
- const previousComponentStack = task.componentStack;
- task.componentStack = createFunctionComponentStack(
- task,
- type.render,
- owner,
- stack,
- );
-
let propsWithoutRef;
if (enableRefAsProp && 'ref' in props) {
// `ref` is just a prop now, but `forwardRef` expects it to not appear in
@@ -1980,7 +1895,6 @@ function renderForwardRef(
actionStateCount,
actionStateMatchingIndex,
);
- task.componentStack = previousComponentStack;
}
function renderMemo(
@@ -1990,24 +1904,13 @@ function renderMemo(
type: any,
props: Object,
ref: any,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
const innerType = type.type;
const resolvedProps = resolveDefaultPropsOnNonClassComponent(
innerType,
props,
);
- renderElement(
- request,
- task,
- keyPath,
- innerType,
- resolvedProps,
- ref,
- owner,
- stack,
- );
+ renderElement(request, task, keyPath, innerType, resolvedProps, ref);
}
function renderContextConsumer(
@@ -2074,12 +1977,7 @@ function renderLazyComponent(
lazyComponent: LazyComponentType,
props: Object,
ref: any,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
- const previousComponentStack = task.componentStack;
- // TODO: Do we really need this stack frame? We don't on the client.
- task.componentStack = createBuiltInComponentStack(task, 'Lazy', owner, stack);
let Component;
if (__DEV__) {
Component = callLazyInitInDEV(lazyComponent);
@@ -2092,17 +1990,7 @@ function renderLazyComponent(
Component,
props,
);
- renderElement(
- request,
- task,
- keyPath,
- Component,
- resolvedProps,
- ref,
- owner,
- stack,
- );
- task.componentStack = previousComponentStack;
+ renderElement(request, task, keyPath, Component, resolvedProps, ref);
}
function renderOffscreen(
@@ -2132,28 +2020,18 @@ function renderElement(
type: any,
props: Object,
ref: any,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
if (typeof type === 'function') {
if (shouldConstruct(type)) {
- renderClassComponent(request, task, keyPath, type, props, owner, stack);
+ renderClassComponent(request, task, keyPath, type, props);
return;
} else {
- renderFunctionComponent(
- request,
- task,
- keyPath,
- type,
- props,
- owner,
- stack,
- );
+ renderFunctionComponent(request, task, keyPath, type, props);
return;
}
}
if (typeof type === 'string') {
- renderHostElement(request, task, keyPath, type, props, owner, stack);
+ renderHostElement(request, task, keyPath, type, props);
return;
}
@@ -2183,19 +2061,11 @@ function renderElement(
return;
}
case REACT_SUSPENSE_LIST_TYPE: {
- const preiousComponentStack = task.componentStack;
- task.componentStack = createBuiltInComponentStack(
- task,
- 'SuspenseList',
- owner,
- stack,
- );
// TODO: SuspenseList should control the boundaries.
const prevKeyPath = task.keyPath;
task.keyPath = keyPath;
renderNodeDestructive(request, task, props.children, -1);
task.keyPath = prevKeyPath;
- task.componentStack = preiousComponentStack;
return;
}
case REACT_SCOPE_TYPE: {
@@ -2213,16 +2083,9 @@ function renderElement(
enableSuspenseAvoidThisFallbackFizz &&
props.unstable_avoidThisFallback === true
) {
- renderBackupSuspenseBoundary(
- request,
- task,
- keyPath,
- props,
- owner,
- stack,
- );
+ renderBackupSuspenseBoundary(request, task, keyPath, props);
} else {
- renderSuspenseBoundary(request, task, keyPath, props, owner, stack);
+ renderSuspenseBoundary(request, task, keyPath, props);
}
return;
}
@@ -2231,20 +2094,11 @@ function renderElement(
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_FORWARD_REF_TYPE: {
- renderForwardRef(
- request,
- task,
- keyPath,
- type,
- props,
- ref,
- owner,
- stack,
- );
+ renderForwardRef(request, task, keyPath, type, props, ref);
return;
}
case REACT_MEMO_TYPE: {
- renderMemo(request, task, keyPath, type, props, ref, owner, stack);
+ renderMemo(request, task, keyPath, type, props, ref);
return;
}
case REACT_PROVIDER_TYPE: {
@@ -2281,16 +2135,7 @@ function renderElement(
// Fall through
}
case REACT_LAZY_TYPE: {
- renderLazyComponent(
- request,
- task,
- keyPath,
- type,
- props,
- ref,
- owner,
- stack,
- );
+ renderLazyComponent(request, task, keyPath, type, props, ref);
return;
}
}
@@ -2370,8 +2215,6 @@ function replayElement(
props: Object,
ref: any,
replay: ReplaySet,
- owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
- stack: null | Error, // DEV only
): void {
// We're replaying. Find the path to follow.
const replayNodes = replay.nodes;
@@ -2399,7 +2242,7 @@ function replayElement(
const currentNode = task.node;
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
try {
- renderElement(request, task, keyPath, type, props, ref, owner, stack);
+ renderElement(request, task, keyPath, type, props, ref);
if (
task.replay.pendingTasks === 1 &&
task.replay.nodes.length > 0
@@ -2466,8 +2309,6 @@ function replayElement(
node[3],
node[4] === null ? [] : node[4][2],
node[4] === null ? null : node[4][3],
- owner,
- stack,
);
}
// We finished rendering this node, so now we can consume this
@@ -2496,7 +2337,7 @@ function validateIterable(
const isGeneratorComponent =
childIndex === -1 && // Only the root child is valid
task.componentStack !== null &&
- task.componentStack.tag === 1 && // FunctionComponent
+ typeof task.componentStack.type === 'function' && // FunctionComponent
// $FlowFixMe[method-unbinding]
Object.prototype.toString.call(task.componentStack.type) ===
'[object GeneratorFunction]' &&
@@ -2543,7 +2384,7 @@ function validateAsyncIterable(
const isGeneratorComponent =
childIndex === -1 && // Only the root child is valid
task.componentStack !== null &&
- task.componentStack.tag === 1 && // FunctionComponent
+ typeof task.componentStack.type === 'function' && // FunctionComponent
// $FlowFixMe[method-unbinding]
Object.prototype.toString.call(task.componentStack.type) ===
'[object AsyncGeneratorFunction]' &&
@@ -2604,6 +2445,24 @@ function renderNodeDestructive(
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;
}
@@ -2628,21 +2487,8 @@ function renderNodeDestructive(
ref = element.ref;
}
- const owner = __DEV__ ? element._owner : null;
- const stack = __DEV__ && enableOwnerStacks ? element._debugStack : null;
-
- let previousDebugTask: null | ConsoleTask = null;
- const previousComponentStack = task.componentStack;
- let debugTask: null | ConsoleTask;
- if (__DEV__) {
- if (enableOwnerStacks) {
- previousDebugTask = task.debugTask;
- }
- pushServerComponentStack(task, element._debugInfo);
- if (enableOwnerStacks) {
- task.debugTask = debugTask = element._debugTask;
- }
- }
+ const debugTask: null | ConsoleTask =
+ __DEV__ && enableOwnerStacks ? task.debugTask : null;
const name = getComponentNameFromType(type);
const keyOrIndex =
@@ -2663,8 +2509,6 @@ function renderNodeDestructive(
props,
ref,
task.replay,
- owner,
- stack,
),
);
} else {
@@ -2679,8 +2523,6 @@ function renderNodeDestructive(
props,
ref,
task.replay,
- owner,
- stack,
);
}
// No matches found for this node. We assume it's already emitted in the
@@ -2697,27 +2539,10 @@ function renderNodeDestructive(
type,
props,
ref,
- owner,
- stack,
),
);
} else {
- renderElement(
- request,
- task,
- keyPath,
- type,
- props,
- ref,
- owner,
- stack,
- );
- }
- }
- if (__DEV__) {
- task.componentStack = previousComponentStack;
- if (enableOwnerStacks) {
- task.debugTask = previousDebugTask;
+ renderElement(request, task, keyPath, type, props, ref);
}
}
return;
@@ -2729,23 +2554,6 @@ function renderNodeDestructive(
);
case REACT_LAZY_TYPE: {
const lazyNode: LazyComponentType = (node: any);
- const previousComponentStack = task.componentStack;
- let previousDebugTask = null;
- if (__DEV__) {
- if (enableOwnerStacks) {
- previousDebugTask = task.debugTask;
- }
- pushServerComponentStack(task, lazyNode._debugInfo);
- }
- if (!__DEV__ || task.componentStack === previousComponentStack) {
- // TODO: Do we really need this stack frame? We don't on the client.
- task.componentStack = createBuiltInComponentStack(
- task,
- 'Lazy',
- null,
- null,
- );
- }
let resolvedNode;
if (__DEV__) {
resolvedNode = callLazyInitInDEV(lazyNode);
@@ -2754,14 +2562,6 @@ function renderNodeDestructive(
const init = lazyNode._init;
resolvedNode = init(payload);
}
-
- // We restore the stack before rendering the resolved node because once the Lazy
- // has resolved any future errors
- task.componentStack = previousComponentStack;
- if (__DEV__ && enableOwnerStacks) {
- task.debugTask = previousDebugTask;
- }
-
// Now we render the resolved node
renderNodeDestructive(request, task, resolvedNode, childIndex);
return;
@@ -2813,15 +2613,6 @@ function renderNodeDestructive(
// for new iterators, but we currently warn for rendering these
// so needs some refactoring to deal with the warning.
- // We need to push a component stack because if this suspends, we'll pop a stack.
- const previousComponentStack = task.componentStack;
- task.componentStack = createBuiltInComponentStack(
- task,
- 'AsyncIterable',
- null,
- null,
- );
-
// Restore the thenable state before resuming.
const prevThenableState = task.thenableState;
task.thenableState = null;
@@ -2859,7 +2650,6 @@ function renderNodeDestructive(
step = unwrapThenable(iterator.next());
}
}
- task.componentStack = previousComponentStack;
renderChildrenArray(request, task, children, childIndex);
return;
}
@@ -2879,19 +2669,12 @@ function renderNodeDestructive(
// Clear any previous thenable state that was created by the unwrapping.
task.thenableState = null;
const thenable: Thenable = (maybeUsable: any);
- const previousComponentStack = task.componentStack;
- if (__DEV__) {
- pushServerComponentStack(task, thenable._debugInfo);
- }
const result = renderNodeDestructive(
request,
task,
unwrapThenable(thenable),
childIndex,
);
- if (__DEV__) {
- task.componentStack = previousComponentStack;
- }
return result;
}
@@ -3069,8 +2852,8 @@ function warnForMissingKey(request: Request, task: Task, child: mixed): void {
const parentOwner = parentStackFrame.owner;
let currentComponentErrorInfo = '';
- if (parentOwner && typeof parentOwner.tag === 'number') {
- const name = getComponentNameFromType((parentOwner: any).type);
+ if (parentOwner && typeof parentOwner.type !== 'undefined') {
+ const name = getComponentNameFromType(parentOwner.type);
if (name) {
currentComponentErrorInfo =
'\n\nCheck the render method of `' + name + '`.';
@@ -3088,8 +2871,8 @@ function warnForMissingKey(request: Request, task: Task, child: mixed): void {
let childOwnerAppendix = '';
if (childOwner != null && parentOwner !== childOwner) {
let ownerName = null;
- if (typeof childOwner.tag === 'number') {
- ownerName = getComponentNameFromType((childOwner: any).type);
+ if (typeof childOwner.type !== 'undefined') {
+ ownerName = getComponentNameFromType(childOwner.type);
} else if (typeof childOwner.name === 'string') {
ownerName = childOwner.name;
}
@@ -3100,8 +2883,9 @@ function warnForMissingKey(request: Request, task: Task, child: mixed): void {
}
// We create a fake component stack for the child to log the stack trace from.
+ const previousComponentStack = task.componentStack;
const stackFrame = createComponentStackFromType(
- task,
+ task.componentStack,
(child: any).type,
(child: any)._owner,
enableOwnerStacks ? (child: any)._debugStack : null,
@@ -3113,7 +2897,7 @@ function warnForMissingKey(request: Request, task: Task, child: mixed): void {
currentComponentErrorInfo,
childOwnerAppendix,
);
- task.componentStack = stackFrame.parent;
+ task.componentStack = previousComponentStack;
}
}
@@ -3448,9 +3232,7 @@ function spawnNewSuspendedReplayTask(
task.formatContext,
task.context,
task.treeContext,
- // We pop one task off the stack because the node that suspended will be tried again,
- // which will add it back onto the stack.
- task.componentStack !== null ? task.componentStack.parent : null,
+ task.componentStack,
task.isFallback,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
@@ -3495,9 +3277,7 @@ function spawnNewSuspendedRenderTask(
task.formatContext,
task.context,
task.treeContext,
- // We pop one task off the stack because the node that suspended will be tried again,
- // which will add it back onto the stack.
- task.componentStack !== null ? task.componentStack.parent : null,
+ task.componentStack,
task.isFallback,
!disableLegacyContext ? task.legacyContext : emptyContextObject,
__DEV__ && enableOwnerStacks ? task.debugTask : null,
@@ -3525,6 +3305,8 @@ function renderNode(
const previousKeyPath = task.keyPath;
const previousTreeContext = task.treeContext;
const previousComponentStack = task.componentStack;
+ const previousDebugTask =
+ __DEV__ && enableOwnerStacks ? task.debugTask : null;
let x;
// Store how much we've pushed at this point so we can reset it in case something
// suspended partially through writing something.
@@ -3569,6 +3351,9 @@ function renderNode(
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
+ if (__DEV__ && enableOwnerStacks) {
+ task.debugTask = previousDebugTask;
+ }
// Restore all active ReactContexts to what they were before.
switchContext(previousContext);
return;
@@ -3623,6 +3408,9 @@ function renderNode(
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
+ if (__DEV__ && enableOwnerStacks) {
+ task.debugTask = previousDebugTask;
+ }
// Restore all active ReactContexts to what they were before.
switchContext(previousContext);
return;
@@ -3659,6 +3447,9 @@ function renderNode(
task.keyPath = previousKeyPath;
task.treeContext = previousTreeContext;
task.componentStack = previousComponentStack;
+ if (__DEV__ && enableOwnerStacks) {
+ task.debugTask = previousDebugTask;
+ }
// Restore all active ReactContexts to what they were before.
switchContext(previousContext);
return;
@@ -4196,7 +3987,7 @@ function retryRenderTask(
// We call the destructive form that mutates this task. That way if something
// suspends again, we can reuse the same task instead of spawning a new one.
- renderNodeDestructive(request, task, task.node, task.childIndex);
+ retryNode(request, task);
pushSegmentFinale(
segment.chunks,
request.renderState,
@@ -4231,11 +4022,6 @@ function retryRenderTask(
const ping = task.ping;
x.then(ping, ping);
task.thenableState = getThenableStateAfterSuspending();
- // We pop one task off the stack because the node that suspended will be tried again,
- // which will add it back onto the stack.
- if (task.componentStack !== null) {
- task.componentStack = task.componentStack.parent;
- }
return;
} else if (
enablePostpone &&
@@ -4299,8 +4085,12 @@ function retryReplayTask(request: Request, task: ReplayTask): void {
try {
// We call the destructive form that mutates this task. That way if something
// suspends again, we can reuse the same task instead of spawning a new one.
-
- renderNodeDestructive(request, task, task.node, task.childIndex);
+ 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(
@@ -4332,11 +4122,6 @@ function retryReplayTask(request: Request, task: ReplayTask): void {
const ping = task.ping;
x.then(ping, ping);
task.thenableState = getThenableStateAfterSuspending();
- // We pop one task off the stack because the node that suspended will be tried again,
- // which will add it back onto the stack.
- if (task.componentStack !== null) {
- task.componentStack = task.componentStack.parent;
- }
return;
}
}
diff --git a/scripts/babel/transform-prevent-infinite-loops.js b/scripts/babel/transform-prevent-infinite-loops.js
index aa88377cc0..635526c14e 100644
--- a/scripts/babel/transform-prevent-infinite-loops.js
+++ b/scripts/babel/transform-prevent-infinite-loops.js
@@ -13,7 +13,7 @@
// This should be reasonable for all loops in the source.
// Note that if the numbers are too large, the tests will take too long to fail
// for this to be useful (each individual test case might hit an infinite loop).
-const MAX_SOURCE_ITERATIONS = 5000;
+const MAX_SOURCE_ITERATIONS = 6000;
// Code in tests themselves is permitted to run longer.
// For example, in the fuzz tester.
const MAX_TEST_ITERATIONS = 5000;