Skip to content

Commit

Permalink
Revert "Suspend Thenable/Lazy if it's used in React.Children and unwr…
Browse files Browse the repository at this point in the history
…ap (#28284)"

This reverts commit 9e7944f.
  • Loading branch information
eps1lon committed Apr 8, 2024
1 parent cb803a1 commit f877090
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 124 deletions.
24 changes: 12 additions & 12 deletions packages/react-reconciler/src/ReactFiberThenable.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,19 @@ export function trackUsedThenable<T>(
}
},
);
}

// Check one more time in case the thenable resolved synchronously.
switch (thenable.status) {
case 'fulfilled': {
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
return fulfilledThenable.value;
}
case 'rejected': {
const rejectedThenable: RejectedThenable<T> = (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<T> = (thenable: any);
return fulfilledThenable.value;
}
case 'rejected': {
const rejectedThenable: RejectedThenable<T> = (thenable: any);
const rejectedError = rejectedThenable.reason;
checkIfUseWrappedInAsyncCatch(rejectedError);
throw rejectedError;
}
}
}

Expand Down
96 changes: 10 additions & 86 deletions packages/react/src/ReactChildren.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -81,68 +75,6 @@ function getElementKey(element: any, index: number): string {
return index.toString(36);
}

function noop() {}

function resolveThenable<T>(thenable: Thenable<T>): 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<T> = (thenable: any);
pendingThenable.status = 'pending';
pendingThenable.then(
fulfilledValue => {
if (thenable.status === 'pending') {
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = fulfilledValue;
}
},
(error: mixed) => {
if (thenable.status === 'pending') {
const rejectedThenable: RejectedThenable<T> = (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<T> = (thenable: any);
return fulfilledThenable.value;
}
case 'rejected': {
const rejectedThenable: RejectedThenable<T> = (thenable: any);
const rejectedError = rejectedThenable.reason;
throw rejectedError;
}
}
}
}
throw thenable;
}

function mapIntoArray(
children: ?ReactNodeList,
array: Array<React$Node>,
Expand Down Expand Up @@ -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.',
);
}
}
Expand Down Expand Up @@ -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]'
Expand Down
42 changes: 16 additions & 26 deletions packages/react/src/__tests__/ReactChildren-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: <div />}));
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(<Component />);
});

expect(container.innerHTML).toBe('<div>hi</div>');
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(<div />);
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(<Component />);
});

expect(container.innerHTML).toBe('<div>hi</div>');
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', () => {
Expand Down

0 comments on commit f877090

Please sign in to comment.