Skip to content

Commit f877090

Browse files
committed
Revert "Suspend Thenable/Lazy if it's used in React.Children and unwrap (facebook#28284)"
This reverts commit 9e7944f.
1 parent cb803a1 commit f877090

File tree

3 files changed

+38
-124
lines changed

3 files changed

+38
-124
lines changed

packages/react-reconciler/src/ReactFiberThenable.js

+12-12
Original file line numberDiff line numberDiff line change
@@ -212,19 +212,19 @@ export function trackUsedThenable<T>(
212212
}
213213
},
214214
);
215-
}
216215

217-
// Check one more time in case the thenable resolved synchronously.
218-
switch (thenable.status) {
219-
case 'fulfilled': {
220-
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
221-
return fulfilledThenable.value;
222-
}
223-
case 'rejected': {
224-
const rejectedThenable: RejectedThenable<T> = (thenable: any);
225-
const rejectedError = rejectedThenable.reason;
226-
checkIfUseWrappedInAsyncCatch(rejectedError);
227-
throw rejectedError;
216+
// Check one more time in case the thenable resolved synchronously.
217+
switch (thenable.status) {
218+
case 'fulfilled': {
219+
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
220+
return fulfilledThenable.value;
221+
}
222+
case 'rejected': {
223+
const rejectedThenable: RejectedThenable<T> = (thenable: any);
224+
const rejectedError = rejectedThenable.reason;
225+
checkIfUseWrappedInAsyncCatch(rejectedError);
226+
throw rejectedError;
227+
}
228228
}
229229
}
230230

packages/react/src/ReactChildren.js

+10-86
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,7 @@
77
* @flow
88
*/
99

10-
import type {
11-
ReactNodeList,
12-
Thenable,
13-
PendingThenable,
14-
FulfilledThenable,
15-
RejectedThenable,
16-
} from 'shared/ReactTypes';
10+
import type {ReactNodeList} from 'shared/ReactTypes';
1711

1812
import isArray from 'shared/isArray';
1913
import {
@@ -81,68 +75,6 @@ function getElementKey(element: any, index: number): string {
8175
return index.toString(36);
8276
}
8377

84-
function noop() {}
85-
86-
function resolveThenable<T>(thenable: Thenable<T>): T {
87-
switch (thenable.status) {
88-
case 'fulfilled': {
89-
const fulfilledValue: T = thenable.value;
90-
return fulfilledValue;
91-
}
92-
case 'rejected': {
93-
const rejectedError = thenable.reason;
94-
throw rejectedError;
95-
}
96-
default: {
97-
if (typeof thenable.status === 'string') {
98-
// Only instrument the thenable if the status if not defined. If
99-
// it's defined, but an unknown value, assume it's been instrumented by
100-
// some custom userspace implementation. We treat it as "pending".
101-
// Attach a dummy listener, to ensure that any lazy initialization can
102-
// happen. Flight lazily parses JSON when the value is actually awaited.
103-
thenable.then(noop, noop);
104-
} else {
105-
// This is an uncached thenable that we haven't seen before.
106-
107-
// TODO: Detect infinite ping loops caused by uncached promises.
108-
109-
const pendingThenable: PendingThenable<T> = (thenable: any);
110-
pendingThenable.status = 'pending';
111-
pendingThenable.then(
112-
fulfilledValue => {
113-
if (thenable.status === 'pending') {
114-
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
115-
fulfilledThenable.status = 'fulfilled';
116-
fulfilledThenable.value = fulfilledValue;
117-
}
118-
},
119-
(error: mixed) => {
120-
if (thenable.status === 'pending') {
121-
const rejectedThenable: RejectedThenable<T> = (thenable: any);
122-
rejectedThenable.status = 'rejected';
123-
rejectedThenable.reason = error;
124-
}
125-
},
126-
);
127-
}
128-
129-
// Check one more time in case the thenable resolved synchronously.
130-
switch (thenable.status) {
131-
case 'fulfilled': {
132-
const fulfilledThenable: FulfilledThenable<T> = (thenable: any);
133-
return fulfilledThenable.value;
134-
}
135-
case 'rejected': {
136-
const rejectedThenable: RejectedThenable<T> = (thenable: any);
137-
const rejectedError = rejectedThenable.reason;
138-
throw rejectedError;
139-
}
140-
}
141-
}
142-
}
143-
throw thenable;
144-
}
145-
14678
function mapIntoArray(
14779
children: ?ReactNodeList,
14880
array: Array<React$Node>,
@@ -175,14 +107,9 @@ function mapIntoArray(
175107
invokeCallback = true;
176108
break;
177109
case REACT_LAZY_TYPE:
178-
const payload = (children: any)._payload;
179-
const init = (children: any)._init;
180-
return mapIntoArray(
181-
init(payload),
182-
array,
183-
escapedPrefix,
184-
nameSoFar,
185-
callback,
110+
throw new Error(
111+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
112+
'We recommend not iterating over children and just rendering them plain.',
186113
);
187114
}
188115
}
@@ -285,19 +212,16 @@ function mapIntoArray(
285212
);
286213
}
287214
} else if (type === 'object') {
215+
// eslint-disable-next-line react-internal/safe-string-coercion
216+
const childrenString = String((children: any));
217+
288218
if (typeof (children: any).then === 'function') {
289-
return mapIntoArray(
290-
resolveThenable((children: any)),
291-
array,
292-
escapedPrefix,
293-
nameSoFar,
294-
callback,
219+
throw new Error(
220+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
221+
'We recommend not iterating over children and just rendering them plain.',
295222
);
296223
}
297224

298-
// eslint-disable-next-line react-internal/safe-string-coercion
299-
const childrenString = String((children: any));
300-
301225
throw new Error(
302226
`Objects are not valid as a React child (found: ${
303227
childrenString === '[object Object]'

packages/react/src/__tests__/ReactChildren-test.js

+16-26
Original file line numberDiff line numberDiff line change
@@ -952,36 +952,26 @@ describe('ReactChildren', () => {
952952
);
953953
});
954954

955-
it('should render React.lazy after suspending', async () => {
955+
it('should throw on React.lazy', async () => {
956956
const lazyElement = React.lazy(async () => ({default: <div />}));
957-
function Component() {
958-
return React.Children.map([lazyElement], c =>
959-
React.cloneElement(c, {children: 'hi'}),
960-
);
961-
}
962-
const container = document.createElement('div');
963-
const root = ReactDOMClient.createRoot(container);
964-
await act(() => {
965-
root.render(<Component />);
966-
});
967-
968-
expect(container.innerHTML).toBe('<div>hi</div>');
957+
await expect(() => {
958+
React.Children.forEach([lazyElement], () => {}, null);
959+
}).toThrowError(
960+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
961+
'We recommend not iterating over children and just rendering them plain.',
962+
{withoutStack: true}, // There's nothing on the stack
963+
);
969964
});
970965

971-
it('should render cached Promises after suspending', async () => {
966+
it('should throw on Promises', async () => {
972967
const promise = Promise.resolve(<div />);
973-
function Component() {
974-
return React.Children.map([promise], c =>
975-
React.cloneElement(c, {children: 'hi'}),
976-
);
977-
}
978-
const container = document.createElement('div');
979-
const root = ReactDOMClient.createRoot(container);
980-
await act(() => {
981-
root.render(<Component />);
982-
});
983-
984-
expect(container.innerHTML).toBe('<div>hi</div>');
968+
await expect(() => {
969+
React.Children.forEach([promise], () => {}, null);
970+
}).toThrowError(
971+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
972+
'We recommend not iterating over children and just rendering them plain.',
973+
{withoutStack: true}, // There's nothing on the stack
974+
);
985975
});
986976

987977
it('should throw on regex', () => {

0 commit comments

Comments
 (0)