@@ -56,7 +56,13 @@ import {
5656 processChildContext ,
5757 emptyContextObject ,
5858} from './ReactFizzContext' ;
59- import { REACT_ELEMENT_TYPE , REACT_SUSPENSE_TYPE } from 'shared/ReactSymbols' ;
59+ import {
60+ getIteratorFn ,
61+ REACT_ELEMENT_TYPE ,
62+ REACT_PORTAL_TYPE ,
63+ REACT_LAZY_TYPE ,
64+ REACT_SUSPENSE_TYPE ,
65+ } from 'shared/ReactSymbols' ;
6066import ReactSharedInternals from 'shared/ReactSharedInternals' ;
6167import {
6268 disableLegacyContext ,
@@ -421,6 +427,16 @@ function shouldConstruct(Component) {
421427 return Component . prototype && Component . prototype . isReactComponent ;
422428}
423429
430+ function invalidRenderResult ( type : any ) : void {
431+ invariant (
432+ false ,
433+ '%s(...): Nothing was returned from render. This usually means a ' +
434+ 'return statement is missing. Or, to render nothing, ' +
435+ 'return null.' ,
436+ getComponentNameFromType ( type ) || 'Component' ,
437+ ) ;
438+ }
439+
424440function renderWithHooks < Props , SecondArg > (
425441 request : Request ,
426442 task : Task ,
@@ -430,6 +446,9 @@ function renderWithHooks<Props, SecondArg>(
430446) : any {
431447 // TODO: Set up Hooks etc.
432448 const children = Component ( props , secondArg ) ;
449+ if ( children === undefined ) {
450+ invalidRenderResult ( Component ) ;
451+ }
433452 return children ;
434453}
435454
@@ -441,6 +460,13 @@ function finishClassComponent(
441460 props : any ,
442461) : ReactNodeList {
443462 const nextChildren = instance . render ( ) ;
463+ if ( nextChildren === undefined ) {
464+ if ( __DEV__ && instance . render . _isMockFunction ) {
465+ // We allow auto-mocks to proceed as if they're returning null.
466+ } else {
467+ invalidRenderResult ( Component ) ;
468+ }
469+ }
444470
445471 if ( __DEV__ ) {
446472 if ( instance . props !== props ) {
@@ -693,6 +719,58 @@ function renderNodeDestructive(
693719 // something suspends.
694720 task . node = node ;
695721
722+ // Handle object types
723+ if ( typeof node === 'object' && node !== null ) {
724+ switch ( ( node : any ) . $$typeof ) {
725+ case REACT_ELEMENT_TYPE : {
726+ const element : React$Element < any > = ( node : any ) ;
727+ const type = element . type ;
728+ const props = element . props ;
729+ renderElement ( request , task , type , props , node ) ;
730+ return ;
731+ }
732+ case REACT_PORTAL_TYPE :
733+ throw new Error ( 'Not yet implemented node type.' ) ;
734+ case REACT_LAZY_TYPE :
735+ throw new Error ( 'Not yet implemented node type.' ) ;
736+ }
737+
738+ if ( isArray ( node ) ) {
739+ if ( node . length > 0 ) {
740+ for ( let i = 0 ; i < node . length ; i ++ ) {
741+ // Recursively render the rest. We need to use the non-destructive form
742+ // so that we can safely pop back up and render the sibling if something
743+ // suspends.
744+ renderNode ( request , task , node [ i ] ) ;
745+ }
746+ } else {
747+ pushEmpty (
748+ task . blockedSegment . chunks ,
749+ request . responseState ,
750+ task . assignID ,
751+ ) ;
752+ task . assignID = null ;
753+ }
754+ return ;
755+ }
756+
757+ const iteratorFn = getIteratorFn ( node ) ;
758+ if ( iteratorFn ) {
759+ throw new Error ( 'Not yet implemented node type.' ) ;
760+ }
761+
762+ const childString = Object . prototype . toString . call ( node ) ;
763+ invariant (
764+ false ,
765+ 'Objects are not valid as a React child (found: %s). ' +
766+ 'If you meant to render a collection of children, use an array ' +
767+ 'instead.' ,
768+ childString === '[object Object]'
769+ ? 'object with keys {' + Object . keys ( node ) . join ( ', ' ) + '}'
770+ : childString ,
771+ ) ;
772+ }
773+
696774 if ( typeof node === 'string' ) {
697775 pushTextInstance (
698776 task . blockedSegment . chunks ,
@@ -704,43 +782,29 @@ function renderNodeDestructive(
704782 return ;
705783 }
706784
707- if ( isArray ( node ) ) {
708- if ( node . length > 0 ) {
709- for ( let i = 0 ; i < node . length ; i ++ ) {
710- // Recursively render the rest. We need to use the non-destructive form
711- // so that we can safely pop back up and render the sibling if something
712- // suspends.
713- renderNode ( request , task , node [ i ] ) ;
714- }
715- } else {
716- pushEmpty (
717- task . blockedSegment . chunks ,
718- request . responseState ,
719- task . assignID ,
720- ) ;
721- task . assignID = null ;
722- }
723- return ;
724- }
725-
726- if ( node === null ) {
727- pushEmpty ( task . blockedSegment . chunks , request . responseState , task . assignID ) ;
785+ if ( typeof node === 'number' ) {
786+ pushTextInstance (
787+ task . blockedSegment . chunks ,
788+ '' + node ,
789+ request . responseState ,
790+ task . assignID ,
791+ ) ;
792+ task . assignID = null ;
728793 return ;
729794 }
730795
731- if (
732- typeof node === 'object' &&
733- node &&
734- ( node : any ) . $$typeof === REACT_ELEMENT_TYPE
735- ) {
736- const element : React$Element < any > = ( node : any ) ;
737- const type = element . type ;
738- const props = element . props ;
739- renderElement ( request , task , type , props , node ) ;
740- return ;
796+ if ( __DEV__ ) {
797+ if ( typeof node === 'function' ) {
798+ console . error (
799+ 'Functions are not valid as a React child. This may happen if ' +
800+ 'you return a Component instead of <Component /> from render. ' +
801+ 'Or maybe you meant to call this function rather than return it.' ,
802+ ) ;
803+ }
741804 }
742805
743- throw new Error ( 'Not yet implemented node type.' ) ;
806+ // Any other type is assumed to be empty.
807+ pushEmpty ( task . blockedSegment . chunks , request . responseState , task . assignID ) ;
744808}
745809
746810function spawnNewSuspendedTask (
0 commit comments