Repository: facebook/react
Pull request: Throw a better error when Lazy/Promise is used in React.Children

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.

Changed files:
filename: packages/react/src/ReactChildren.js
patch: @@ -13,6 +13,7 @@ import isArray from 'shared/isArray';
 import {
   getIteratorFn,
   REACT_ELEMENT_TYPE,
+  REACT_LAZY_TYPE,
   REACT_PORTAL_TYPE,
 } from 'shared/ReactSymbols';
 import {checkKeyStringCoercion} from 'shared/CheckStringCoercion';
@@ -103,6 +104,12 @@ function mapIntoArray(
           case REACT_ELEMENT_TYPE:
           case REACT_PORTAL_TYPE:
             invokeCallback = true;
+            break;
+          case REACT_LAZY_TYPE:
+            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.',
+            );
         }
     }
   }
@@ -207,6 +214,13 @@ function mapIntoArray(
       // eslint-disable-next-line react-internal/safe-string-coercion
       const childrenString = String((children: any));
 
+      if (typeof (children: any).then === 'function') {
+        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.',
+        );
+      }
+
       throw new Error(
         `Objects are not valid as a React child (found: ${
           childrenString === '[object Object]'

filename: packages/react/src/__tests__/ReactChildren-test.js
patch: @@ -948,6 +948,28 @@ describe('ReactChildren', () => {
     );
   });
 
+  it('should throw on React.lazy', async () => {
+    const lazyElement = React.lazy(async () => ({default: <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 throw on Promises', async () => {
+    const promise = Promise.resolve(<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', () => {
     // Really, we care about dates (#4840) but those have nondeterministic
     // serialization (timezones) so let's test a regex instead:

filename: scripts/error-codes/codes.json
patch: @@ -489,5 +489,6 @@
   "501": "The render was aborted with postpone when the shell is incomplete. Reason: %s",
   "502": "Cannot read a Client Context from a Server Component.",
   "503": "Cannot use() an already resolved Client Reference.",
-  "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."
+  "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.",
+  "505": "Cannot render an Async Component, Promise or React.Lazy inside React.Children. We recommend not iterating over children and just rendering them plain."
 }