@@ -550,39 +550,44 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
550550 function warnOnDuplicateKey (
551551 child : mixed ,
552552 returnFiber : Fiber ,
553- knownKeys : Set < string >
554- ) {
553+ knownKeys : Set < string > | null
554+ ) : Set < string > | null {
555555 if ( __DEV__ ) {
556- if ( child == null || typeof child ! == 'object' ) {
557- return ;
556+ if ( typeof child ! == 'object' || child == null ) {
557+ return knownKeys ;
558558 }
559559 switch ( child . $$typeof ) {
560- // They have keys.
561560 case REACT_ELEMENT_TYPE :
562561 case REACT_COROUTINE_TYPE :
563562 case REACT_YIELD_TYPE :
564563 case REACT_PORTAL_TYPE :
565564 const key = child . key ;
566565 if ( typeof key !== 'string' ) {
567- return ;
566+ break ;
568567 }
569- if ( knownKeys . has ( key ) ) {
570- warning (
571- false ,
572- 'Encountered two children with the same key, ' +
573- '`%s`. Child keys must be unique; when two children share a key, ' +
574- 'only the first child will be used.%s' ,
575- key ,
576- getStackAddendumByFiber ( returnFiber )
577- ) ;
578- } else {
568+ if ( knownKeys == null ) {
569+ knownKeys = new Set ( ) ;
570+ knownKeys . add ( key ) ;
571+ break ;
572+ }
573+ if ( ! knownKeys . has ( key ) ) {
579574 knownKeys . add ( key ) ;
575+ break ;
580576 }
581- return ;
577+ warning (
578+ false ,
579+ 'Encountered two children with the same key, ' +
580+ '`%s`. Child keys must be unique; when two children share a key, ' +
581+ 'only the first child will be used.%s' ,
582+ key ,
583+ getStackAddendumByFiber ( returnFiber )
584+ ) ;
585+ break ;
582586 default :
583- return ;
587+ break ;
584588 }
585589 }
590+ return knownKeys ;
586591 }
587592
588593 function reconcileChildrenArray (
@@ -610,11 +615,18 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
610615 // If you change this code, also update reconcileChildrenIterator() which
611616 // uses the same algorithm.
612617
618+ if ( __DEV__ ) {
619+ // First, validate keys.
620+ let knownKeys = null ;
621+ for ( let i = 0 ; i < newChildren . length ; i ++ ) {
622+ const child = newChildren [ i ] ;
623+ knownKeys = warnOnDuplicateKey ( child , returnFiber , knownKeys ) ;
624+ }
625+ }
626+
613627 let resultingFirstChild : ?Fiber = null ;
614628 let previousNewFiber : ?Fiber = null ;
615629
616- let knownKeysInDev = null ;
617-
618630 let oldFiber = currentFirstChild ;
619631 let lastPlacedIndex = 0 ;
620632 let newIdx = 0 ;
@@ -628,15 +640,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
628640 nextOldFiber = oldFiber . sibling ;
629641 }
630642 }
631- const newChild = newChildren [ newIdx ] ;
632- if ( __DEV__ ) {
633- knownKeysInDev = knownKeysInDev || new Set ( ) ;
634- warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
635- }
636643 const newFiber = updateSlot (
637644 returnFiber ,
638645 oldFiber ,
639- newChild ,
646+ newChildren [ newIdx ] ,
640647 priority
641648 ) ;
642649 if ( ! newFiber ) {
@@ -647,18 +654,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
647654 if ( ! oldFiber ) {
648655 oldFiber = nextOldFiber ;
649656 }
650- if ( __DEV__ ) {
651- if (
652- knownKeysInDev != null &&
653- newChild != null &&
654- typeof newChild . key === 'string'
655- ) {
656- // Since we break out of this loop without incrementing newIdx,
657- // we will look at this child again in the next loop.
658- // Forget we checked it to avoid a false positive for duplicate key.
659- knownKeysInDev . delete ( newChild . key ) ;
660- }
661- }
662657 break ;
663658 }
664659 if ( shouldTrackSideEffects ) {
@@ -693,14 +688,9 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
693688 // If we don't have any more existing children we can choose a fast path
694689 // since the rest will all be insertions.
695690 for ( ; newIdx < newChildren . length ; newIdx ++ ) {
696- const newChild = newChildren [ newIdx ] ;
697- if ( __DEV__ ) {
698- knownKeysInDev = knownKeysInDev || new Set ( ) ;
699- warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
700- }
701691 const newFiber = createChild (
702692 returnFiber ,
703- newChild ,
693+ newChildren [ newIdx ] ,
704694 priority
705695 ) ;
706696 if ( ! newFiber ) {
@@ -723,16 +713,11 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
723713
724714 // Keep scanning and use the map to restore deleted items as moves.
725715 for ( ; newIdx < newChildren . length ; newIdx ++ ) {
726- const newChild = newChildren [ newIdx ] ;
727- if ( __DEV__ ) {
728- knownKeysInDev = knownKeysInDev || new Set ( ) ;
729- warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
730- }
731716 const newFiber = updateFromMap (
732717 existingChildren ,
733718 returnFiber ,
734719 newIdx ,
735- newChild ,
720+ newChildren [ newIdx ] ,
736721 priority
737722 ) ;
738723 if ( newFiber ) {
@@ -769,17 +754,40 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
769754 function reconcileChildrenIterator (
770755 returnFiber : Fiber ,
771756 currentFirstChild : ?Fiber ,
772- newChildren : Iterator < * > ,
757+ newChildrenIterable : Iterable < * > ,
773758 priority : PriorityLevel ) : ?Fiber {
774759
775760 // This is the same implementation as reconcileChildrenArray(),
776761 // but using the iterator instead.
777762
763+ const iteratorFn = getIteratorFn ( newChildrenIterable ) ;
764+ if ( typeof iteratorFn !== 'function' ) {
765+ throw new Error ( 'An object is not an iterable.' ) ;
766+ }
767+
768+ if ( __DEV__ ) {
769+ // First, validate keys.
770+ // We'll get a different iterator later for the main pass.
771+ const newChildren = iteratorFn . call ( newChildrenIterable ) ;
772+ if ( newChildren == null ) {
773+ throw new Error ( 'An iterable object provided no iterator.' ) ;
774+ }
775+ let knownKeys = null ;
776+ let step = newChildren . next ( ) ;
777+ for ( ; ! step . done ; step = newChildren . next ( ) ) {
778+ const child = step . value ;
779+ knownKeys = warnOnDuplicateKey ( child , returnFiber , knownKeys ) ;
780+ }
781+ }
782+
783+ const newChildren = iteratorFn . call ( newChildrenIterable ) ;
784+ if ( newChildren == null ) {
785+ throw new Error ( 'An iterable object provided no iterator.' ) ;
786+ }
787+
778788 let resultingFirstChild : ?Fiber = null ;
779789 let previousNewFiber : ?Fiber = null ;
780790
781- let knownKeysInDev = null ;
782-
783791 let oldFiber = currentFirstChild ;
784792 let lastPlacedIndex = 0 ;
785793 let newIdx = 0 ;
@@ -795,15 +803,10 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
795803 nextOldFiber = oldFiber . sibling ;
796804 }
797805 }
798- const newChild = step . value ;
799- if ( __DEV__ ) {
800- knownKeysInDev = knownKeysInDev || new Set ( ) ;
801- warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
802- }
803806 const newFiber = updateSlot (
804807 returnFiber ,
805808 oldFiber ,
806- newChild ,
809+ step . value ,
807810 priority
808811 ) ;
809812 if ( ! newFiber ) {
@@ -814,18 +817,6 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
814817 if ( ! oldFiber ) {
815818 oldFiber = nextOldFiber ;
816819 }
817- if ( __DEV__ ) {
818- if (
819- knownKeysInDev != null &&
820- newChild != null &&
821- typeof newChild . key === 'string'
822- ) {
823- // Since we break out of this loop without incrementing newIdx,
824- // we will look at this child again in the next loop.
825- // Forget we checked it to avoid a false positive for duplicate key.
826- knownKeysInDev . delete ( newChild . key ) ;
827- }
828- }
829820 break ;
830821 }
831822 if ( shouldTrackSideEffects ) {
@@ -860,14 +851,9 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
860851 // If we don't have any more existing children we can choose a fast path
861852 // since the rest will all be insertions.
862853 for ( ; ! step . done ; newIdx ++ , step = newChildren . next ( ) ) {
863- const newChild = step . value ;
864- if ( __DEV__ ) {
865- knownKeysInDev = knownKeysInDev || new Set ( ) ;
866- warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
867- }
868854 const newFiber = createChild (
869855 returnFiber ,
870- newChild ,
856+ step . value ,
871857 priority
872858 ) ;
873859 if ( ! newFiber ) {
@@ -890,16 +876,11 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
890876
891877 // Keep scanning and use the map to restore deleted items as moves.
892878 for ( ; ! step . done ; newIdx ++ , step = newChildren . next ( ) ) {
893- const newChild = step . value ;
894- if ( __DEV__ ) {
895- knownKeysInDev = knownKeysInDev || new Set ( ) ;
896- warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
897- }
898879 const newFiber = updateFromMap (
899880 existingChildren ,
900881 returnFiber ,
901882 newIdx ,
902- newChild ,
883+ step . value ,
903884 priority
904885 ) ;
905886 if ( newFiber ) {
@@ -1172,16 +1153,11 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
11721153 ) ;
11731154 }
11741155
1175- const iteratorFn = getIteratorFn ( newChild ) ;
1176- if ( iteratorFn ) {
1177- const iterator = iteratorFn . call ( newChild ) ;
1178- if ( iterator == null ) {
1179- throw new Error ( 'An iterable object provided no iterator.' ) ;
1180- }
1156+ if ( getIteratorFn ( newChild ) ) {
11811157 return reconcileChildrenIterator (
11821158 returnFiber ,
11831159 currentFirstChild ,
1184- iterator ,
1160+ newChild ,
11851161 priority
11861162 ) ;
11871163 }
0 commit comments