diff --git a/packages/react-reconciler/src/ReactFiberThenable.js b/packages/react-reconciler/src/ReactFiberThenable.js index e34d9ad5e2275..956f81e89270a 100644 --- a/packages/react-reconciler/src/ReactFiberThenable.js +++ b/packages/react-reconciler/src/ReactFiberThenable.js @@ -212,19 +212,19 @@ export function trackUsedThenable( } }, ); - } - // Check one more time in case the thenable resolved synchronously. - switch (thenable.status) { - case 'fulfilled': { - const fulfilledThenable: FulfilledThenable = (thenable: any); - return fulfilledThenable.value; - } - case 'rejected': { - const rejectedThenable: RejectedThenable = (thenable: any); - const rejectedError = rejectedThenable.reason; - checkIfUseWrappedInAsyncCatch(rejectedError); - throw rejectedError; + // Check one more time in case the thenable resolved synchronously. + switch (thenable.status) { + case 'fulfilled': { + const fulfilledThenable: FulfilledThenable = (thenable: any); + return fulfilledThenable.value; + } + case 'rejected': { + const rejectedThenable: RejectedThenable = (thenable: any); + const rejectedError = rejectedThenable.reason; + checkIfUseWrappedInAsyncCatch(rejectedError); + throw rejectedError; + } } } diff --git a/packages/react/src/ReactChildren.js b/packages/react/src/ReactChildren.js index d671c1503327d..d8d2f6707a95d 100644 --- a/packages/react/src/ReactChildren.js +++ b/packages/react/src/ReactChildren.js @@ -7,13 +7,7 @@ * @flow */ -import type { - ReactNodeList, - Thenable, - PendingThenable, - FulfilledThenable, - RejectedThenable, -} from 'shared/ReactTypes'; +import type {ReactNodeList} from 'shared/ReactTypes'; import isArray from 'shared/isArray'; import { @@ -81,68 +75,6 @@ function getElementKey(element: any, index: number): string { return index.toString(36); } -function noop() {} - -function resolveThenable(thenable: Thenable): T { - switch (thenable.status) { - case 'fulfilled': { - const fulfilledValue: T = thenable.value; - return fulfilledValue; - } - case 'rejected': { - const rejectedError = thenable.reason; - throw rejectedError; - } - default: { - if (typeof thenable.status === 'string') { - // Only instrument the thenable if the status if not defined. If - // it's defined, but an unknown value, assume it's been instrumented by - // some custom userspace implementation. We treat it as "pending". - // Attach a dummy listener, to ensure that any lazy initialization can - // happen. Flight lazily parses JSON when the value is actually awaited. - thenable.then(noop, noop); - } else { - // This is an uncached thenable that we haven't seen before. - - // TODO: Detect infinite ping loops caused by uncached promises. - - const pendingThenable: PendingThenable = (thenable: any); - pendingThenable.status = 'pending'; - pendingThenable.then( - fulfilledValue => { - if (thenable.status === 'pending') { - const fulfilledThenable: FulfilledThenable = (thenable: any); - fulfilledThenable.status = 'fulfilled'; - fulfilledThenable.value = fulfilledValue; - } - }, - (error: mixed) => { - if (thenable.status === 'pending') { - const rejectedThenable: RejectedThenable = (thenable: any); - rejectedThenable.status = 'rejected'; - rejectedThenable.reason = error; - } - }, - ); - } - - // Check one more time in case the thenable resolved synchronously. - switch (thenable.status) { - case 'fulfilled': { - const fulfilledThenable: FulfilledThenable = (thenable: any); - return fulfilledThenable.value; - } - case 'rejected': { - const rejectedThenable: RejectedThenable = (thenable: any); - const rejectedError = rejectedThenable.reason; - throw rejectedError; - } - } - } - } - throw thenable; -} - function mapIntoArray( children: ?ReactNodeList, array: Array, @@ -175,14 +107,9 @@ function mapIntoArray( invokeCallback = true; break; case REACT_LAZY_TYPE: - const payload = (children: any)._payload; - const init = (children: any)._init; - return mapIntoArray( - init(payload), - array, - escapedPrefix, - nameSoFar, - callback, + throw new Error( + 'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' + + 'We recommend not iterating over children and just rendering them plain.', ); } } @@ -285,19 +212,16 @@ function mapIntoArray( ); } } else if (type === 'object') { + // eslint-disable-next-line react-internal/safe-string-coercion + const childrenString = String((children: any)); + if (typeof (children: any).then === 'function') { - return mapIntoArray( - resolveThenable((children: any)), - array, - escapedPrefix, - nameSoFar, - callback, + throw new Error( + 'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' + + 'We recommend not iterating over children and just rendering them plain.', ); } - // eslint-disable-next-line react-internal/safe-string-coercion - const childrenString = String((children: any)); - throw new Error( `Objects are not valid as a React child (found: ${ childrenString === '[object Object]' diff --git a/packages/react/src/__tests__/ReactChildren-test.js b/packages/react/src/__tests__/ReactChildren-test.js index 92e7ba435a1d2..0c405f91b15f3 100644 --- a/packages/react/src/__tests__/ReactChildren-test.js +++ b/packages/react/src/__tests__/ReactChildren-test.js @@ -952,36 +952,26 @@ describe('ReactChildren', () => { ); }); - it('should render React.lazy after suspending', async () => { + it('should throw on React.lazy', async () => { const lazyElement = React.lazy(async () => ({default:
})); - function Component() { - return React.Children.map([lazyElement], c => - React.cloneElement(c, {children: 'hi'}), - ); - } - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - - expect(container.innerHTML).toBe('
hi
'); + await expect(() => { + React.Children.forEach([lazyElement], () => {}, null); + }).toThrowError( + 'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' + + 'We recommend not iterating over children and just rendering them plain.', + {withoutStack: true}, // There's nothing on the stack + ); }); - it('should render cached Promises after suspending', async () => { + it('should throw on Promises', async () => { const promise = Promise.resolve(
); - function Component() { - return React.Children.map([promise], c => - React.cloneElement(c, {children: 'hi'}), - ); - } - const container = document.createElement('div'); - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - - expect(container.innerHTML).toBe('
hi
'); + await expect(() => { + React.Children.forEach([promise], () => {}, null); + }).toThrowError( + 'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' + + 'We recommend not iterating over children and just rendering them plain.', + {withoutStack: true}, // There's nothing on the stack + ); }); it('should throw on regex', () => {