Skip to content

Commit 7a98d96

Browse files
sebmarkbageAndyPengc12
authored andcommitted
Throw a better error when Lazy/Promise is used in React.Children (facebook#28280)
We could in theory actually support this case by throwing a Promise when it's used inside a render. Allowing it to be synchronously unwrapped. However, it's a bit sketchy because we officially only support this in the render's child position or in `use()`. Another alternative could be to actually pass the Promise/Lazy to the callback so that you can reason about it and just return it again or even unwrapping with `use()` - at least for the forEach case maybe.
1 parent a984e67 commit 7a98d96

File tree

3 files changed

+38
-1
lines changed

3 files changed

+38
-1
lines changed

packages/react/src/ReactChildren.js

+14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import isArray from 'shared/isArray';
1313
import {
1414
getIteratorFn,
1515
REACT_ELEMENT_TYPE,
16+
REACT_LAZY_TYPE,
1617
REACT_PORTAL_TYPE,
1718
} from 'shared/ReactSymbols';
1819
import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
@@ -103,6 +104,12 @@ function mapIntoArray(
103104
case REACT_ELEMENT_TYPE:
104105
case REACT_PORTAL_TYPE:
105106
invokeCallback = true;
107+
break;
108+
case REACT_LAZY_TYPE:
109+
throw new Error(
110+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
111+
'We recommend not iterating over children and just rendering them plain.',
112+
);
106113
}
107114
}
108115
}
@@ -207,6 +214,13 @@ function mapIntoArray(
207214
// eslint-disable-next-line react-internal/safe-string-coercion
208215
const childrenString = String((children: any));
209216

217+
if (typeof (children: any).then === 'function') {
218+
throw new Error(
219+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
220+
'We recommend not iterating over children and just rendering them plain.',
221+
);
222+
}
223+
210224
throw new Error(
211225
`Objects are not valid as a React child (found: ${
212226
childrenString === '[object Object]'

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

+22
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,28 @@ describe('ReactChildren', () => {
948948
);
949949
});
950950

951+
it('should throw on React.lazy', async () => {
952+
const lazyElement = React.lazy(async () => ({default: <div />}));
953+
await expect(() => {
954+
React.Children.forEach([lazyElement], () => {}, null);
955+
}).toThrowError(
956+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
957+
'We recommend not iterating over children and just rendering them plain.',
958+
{withoutStack: true}, // There's nothing on the stack
959+
);
960+
});
961+
962+
it('should throw on Promises', async () => {
963+
const promise = Promise.resolve(<div />);
964+
await expect(() => {
965+
React.Children.forEach([promise], () => {}, null);
966+
}).toThrowError(
967+
'Cannot render an Async Component, Promise or React.Lazy inside React.Children. ' +
968+
'We recommend not iterating over children and just rendering them plain.',
969+
{withoutStack: true}, // There's nothing on the stack
970+
);
971+
});
972+
951973
it('should throw on regex', () => {
952974
// Really, we care about dates (#4840) but those have nondeterministic
953975
// serialization (timezones) so let's test a regex instead:

scripts/error-codes/codes.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -489,5 +489,6 @@
489489
"501": "The render was aborted with postpone when the shell is incomplete. Reason: %s",
490490
"502": "Cannot read a Client Context from a Server Component.",
491491
"503": "Cannot use() an already resolved Client Reference.",
492-
"504": "Failed to read a RSC payload created by a development version of React on the server while using a production version on the client. Always use matching versions on the server and the client."
492+
"504": "Failed to read a RSC payload created by a development version of React on the server while using a production version on the client. Always use matching versions on the server and the client.",
493+
"505": "Cannot render an Async Component, Promise or React.Lazy inside React.Children. We recommend not iterating over children and just rendering them plain."
493494
}

0 commit comments

Comments
 (0)