@@ -56,7 +56,13 @@ import {
56
56
processChildContext ,
57
57
emptyContextObject ,
58
58
} 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' ;
60
66
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
61
67
import {
62
68
disableLegacyContext ,
@@ -421,6 +427,16 @@ function shouldConstruct(Component) {
421
427
return Component . prototype && Component . prototype . isReactComponent ;
422
428
}
423
429
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
+
424
440
function renderWithHooks < Props , SecondArg > (
425
441
request : Request ,
426
442
task : Task ,
@@ -430,6 +446,9 @@ function renderWithHooks<Props, SecondArg>(
430
446
) : any {
431
447
// TODO: Set up Hooks etc.
432
448
const children = Component ( props , secondArg ) ;
449
+ if ( children === undefined ) {
450
+ invalidRenderResult ( Component ) ;
451
+ }
433
452
return children ;
434
453
}
435
454
@@ -441,6 +460,13 @@ function finishClassComponent(
441
460
props : any ,
442
461
) : ReactNodeList {
443
462
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
+ }
444
470
445
471
if ( __DEV__ ) {
446
472
if ( instance . props !== props ) {
@@ -693,6 +719,58 @@ function renderNodeDestructive(
693
719
// something suspends.
694
720
task . node = node ;
695
721
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
+
696
774
if ( typeof node === 'string' ) {
697
775
pushTextInstance (
698
776
task . blockedSegment . chunks ,
@@ -704,43 +782,29 @@ function renderNodeDestructive(
704
782
return ;
705
783
}
706
784
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 ;
728
793
return ;
729
794
}
730
795
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
+ }
741
804
}
742
805
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 ) ;
744
808
}
745
809
746
810
function spawnNewSuspendedTask (
0 commit comments