@@ -62,6 +62,12 @@ import {
62
62
REACT_PORTAL_TYPE ,
63
63
REACT_LAZY_TYPE ,
64
64
REACT_SUSPENSE_TYPE ,
65
+ REACT_LEGACY_HIDDEN_TYPE ,
66
+ REACT_DEBUG_TRACING_MODE_TYPE ,
67
+ REACT_STRICT_MODE_TYPE ,
68
+ REACT_PROFILER_TYPE ,
69
+ REACT_SUSPENSE_LIST_TYPE ,
70
+ REACT_FRAGMENT_TYPE ,
65
71
} from 'shared/ReactSymbols' ;
66
72
import ReactSharedInternals from 'shared/ReactSharedInternals' ;
67
73
import {
@@ -521,6 +527,8 @@ const didWarnAboutContextTypeOnFunctionComponent = {};
521
527
const didWarnAboutGetDerivedStateOnFunctionComponent = { } ;
522
528
let didWarnAboutReassigningProps = false ;
523
529
const didWarnAboutDefaultPropsOnFunctionComponent = { } ;
530
+ let didWarnAboutGenerators = false ;
531
+ let didWarnAboutMaps = false ;
524
532
525
533
// This would typically be a function component but we still support module pattern
526
534
// components for some reason.
@@ -701,10 +709,67 @@ function renderElement(
701
709
}
702
710
} else if ( typeof type === 'string' ) {
703
711
renderHostElement ( request , task , type , props ) ;
704
- } else if ( type === REACT_SUSPENSE_TYPE ) {
705
- renderSuspenseBoundary ( request , task , props ) ;
706
712
} else {
707
- throw new Error ( 'Not yet implemented element type.' ) ;
713
+ switch ( type ) {
714
+ // TODO: LegacyHidden acts the same as a fragment. This only works
715
+ // because we currently assume that every instance of LegacyHidden is
716
+ // accompanied by a host component wrapper. In the hidden mode, the host
717
+ // component is given a `hidden` attribute, which ensures that the
718
+ // initial HTML is not visible. To support the use of LegacyHidden as a
719
+ // true fragment, without an extra DOM node, we would have to hide the
720
+ // initial HTML in some other way.
721
+ // TODO: Add REACT_OFFSCREEN_TYPE here too with the same capability.
722
+ case REACT_LEGACY_HIDDEN_TYPE :
723
+ case REACT_DEBUG_TRACING_MODE_TYPE :
724
+ case REACT_STRICT_MODE_TYPE :
725
+ case REACT_PROFILER_TYPE :
726
+ case REACT_SUSPENSE_LIST_TYPE : // TODO: SuspenseList should control the boundaries.
727
+ case REACT_FRAGMENT_TYPE : {
728
+ renderNodeDestructive ( request , task , props . children ) ;
729
+ break ;
730
+ }
731
+ case REACT_SUSPENSE_TYPE : {
732
+ renderSuspenseBoundary ( request , task , props ) ;
733
+ break ;
734
+ }
735
+ default : {
736
+ throw new Error ( 'Not yet implemented element type.' ) ;
737
+ }
738
+ }
739
+ }
740
+ }
741
+
742
+ function validateIterable ( iterable , iteratorFn : Function ) : void {
743
+ if ( __DEV__ ) {
744
+ // We don't support rendering Generators because it's a mutation.
745
+ // See https://github.com/facebook/react/issues/12995
746
+ if (
747
+ typeof Symbol === 'function' &&
748
+ // $FlowFixMe Flow doesn't know about toStringTag
749
+ iterable [ Symbol . toStringTag ] === 'Generator'
750
+ ) {
751
+ if ( ! didWarnAboutGenerators ) {
752
+ console . error (
753
+ 'Using Generators as children is unsupported and will likely yield ' +
754
+ 'unexpected results because enumerating a generator mutates it. ' +
755
+ 'You may convert it to an array with `Array.from()` or the ' +
756
+ '`[...spread]` operator before rendering. Keep in mind ' +
757
+ 'you might need to polyfill these features for older browsers.' ,
758
+ ) ;
759
+ }
760
+ didWarnAboutGenerators = true ;
761
+ }
762
+
763
+ // Warn about using Maps as children
764
+ if ( ( iterable : any ) . entries === iteratorFn ) {
765
+ if ( ! didWarnAboutMaps ) {
766
+ console . error (
767
+ 'Using Maps as children is not supported. ' +
768
+ 'Use an array of keyed ReactElements instead.' ,
769
+ ) ;
770
+ }
771
+ didWarnAboutMaps = true ;
772
+ }
708
773
}
709
774
}
710
775
@@ -756,7 +821,30 @@ function renderNodeDestructive(
756
821
757
822
const iteratorFn = getIteratorFn ( node ) ;
758
823
if ( iteratorFn ) {
759
- throw new Error ( 'Not yet implemented node type.' ) ;
824
+ if ( __DEV__ ) {
825
+ validateIterable ( node , iteratorFn ( ) ) ;
826
+ }
827
+ const iterator = iteratorFn . call ( node ) ;
828
+ if ( iterator ) {
829
+ let step = iterator . next ( ) ;
830
+ // If there are not entries, we need to push an empty so we start by checking that.
831
+ if ( ! step . done ) {
832
+ do {
833
+ // Recursively render the rest. We need to use the non-destructive form
834
+ // so that we can safely pop back up and render the sibling if something
835
+ // suspends.
836
+ renderNode ( request , task , step . value ) ;
837
+ step = iterator . next ( ) ;
838
+ } while ( ! step . done ) ;
839
+ return ;
840
+ }
841
+ }
842
+ pushEmpty (
843
+ task . blockedSegment . chunks ,
844
+ request . responseState ,
845
+ task . assignID ,
846
+ ) ;
847
+ task . assignID = null ;
760
848
}
761
849
762
850
const childString = Object . prototype . toString . call ( node ) ;
@@ -805,6 +893,7 @@ function renderNodeDestructive(
805
893
806
894
// Any other type is assumed to be empty.
807
895
pushEmpty ( task . blockedSegment . chunks , request . responseState , task . assignID ) ;
896
+ task . assignID = null ;
808
897
}
809
898
810
899
function spawnNewSuspendedTask (
0 commit comments