@@ -37,6 +37,12 @@ var emptyObject = require('emptyObject');
3737var getIteratorFn = require ( 'getIteratorFn' ) ;
3838var invariant = require ( 'invariant' ) ;
3939
40+ if ( __DEV__ ) {
41+ var ReactComponentTreeHook = require ( 'ReactComponentTreeHook' ) ;
42+ var { getStackAddendumByFiber } = ReactComponentTreeHook ;
43+ var warning = require ( 'warning' ) ;
44+ }
45+
4046const {
4147 cloneFiber,
4248 createFiberFromElement,
@@ -541,6 +547,44 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
541547 return null ;
542548 }
543549
550+ function warnOnDuplicateKey (
551+ child : mixed ,
552+ returnFiber : Fiber ,
553+ knownKeys : Set < string >
554+ ) {
555+ if ( __DEV__ ) {
556+ if ( child == null || typeof child !== 'object' ) {
557+ return ;
558+ }
559+ switch ( child . $$typeof ) {
560+ // They have keys.
561+ case REACT_ELEMENT_TYPE :
562+ case REACT_COROUTINE_TYPE :
563+ case REACT_YIELD_TYPE :
564+ case REACT_PORTAL_TYPE :
565+ const key = child . key ;
566+ if ( typeof key !== 'string' ) {
567+ return ;
568+ }
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 {
579+ knownKeys . add ( key ) ;
580+ }
581+ return ;
582+ default :
583+ return ;
584+ }
585+ }
586+ }
587+
544588 function reconcileChildrenArray (
545589 returnFiber : Fiber ,
546590 currentFirstChild : ?Fiber ,
@@ -569,6 +613,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
569613 let resultingFirstChild : ?Fiber = null ;
570614 let previousNewFiber : ?Fiber = null ;
571615
616+ let knownKeysInDev = null ;
617+
572618 let oldFiber = currentFirstChild ;
573619 let lastPlacedIndex = 0 ;
574620 let newIdx = 0 ;
@@ -582,10 +628,15 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
582628 nextOldFiber = oldFiber . sibling ;
583629 }
584630 }
631+ const newChild = newChildren [ newIdx ] ;
632+ if ( __DEV__ ) {
633+ knownKeysInDev = knownKeysInDev || new Set ( ) ;
634+ warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
635+ }
585636 const newFiber = updateSlot (
586637 returnFiber ,
587638 oldFiber ,
588- newChildren [ newIdx ] ,
639+ newChild ,
589640 priority
590641 ) ;
591642 if ( ! newFiber ) {
@@ -596,6 +647,18 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
596647 if ( ! oldFiber ) {
597648 oldFiber = nextOldFiber ;
598649 }
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+ }
599662 break ;
600663 }
601664 if ( shouldTrackSideEffects ) {
@@ -630,9 +693,14 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
630693 // If we don't have any more existing children we can choose a fast path
631694 // since the rest will all be insertions.
632695 for ( ; newIdx < newChildren . length ; newIdx ++ ) {
696+ const newChild = newChildren [ newIdx ] ;
697+ if ( __DEV__ ) {
698+ knownKeysInDev = knownKeysInDev || new Set ( ) ;
699+ warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
700+ }
633701 const newFiber = createChild (
634702 returnFiber ,
635- newChildren [ newIdx ] ,
703+ newChild ,
636704 priority
637705 ) ;
638706 if ( ! newFiber ) {
@@ -655,11 +723,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
655723
656724 // Keep scanning and use the map to restore deleted items as moves.
657725 for ( ; newIdx < newChildren . length ; newIdx ++ ) {
726+ const newChild = newChildren [ newIdx ] ;
727+ if ( __DEV__ ) {
728+ knownKeysInDev = knownKeysInDev || new Set ( ) ;
729+ warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
730+ }
658731 const newFiber = updateFromMap (
659732 existingChildren ,
660733 returnFiber ,
661734 newIdx ,
662- newChildren [ newIdx ] ,
735+ newChild ,
663736 priority
664737 ) ;
665738 if ( newFiber ) {
@@ -705,6 +778,8 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
705778 let resultingFirstChild : ?Fiber = null ;
706779 let previousNewFiber : ?Fiber = null ;
707780
781+ let knownKeysInDev = null ;
782+
708783 let oldFiber = currentFirstChild ;
709784 let lastPlacedIndex = 0 ;
710785 let newIdx = 0 ;
@@ -720,10 +795,15 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
720795 nextOldFiber = oldFiber . sibling ;
721796 }
722797 }
798+ const newChild = step . value ;
799+ if ( __DEV__ ) {
800+ knownKeysInDev = knownKeysInDev || new Set ( ) ;
801+ warnOnDuplicateKey ( newChild , returnFiber , knownKeysInDev ) ;
802+ }
723803 const newFiber = updateSlot (
724804 returnFiber ,
725805 oldFiber ,
726- step . value ,
806+ newChild ,
727807 priority
728808 ) ;
729809 if ( ! newFiber ) {
@@ -734,6 +814,18 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
734814 if ( ! oldFiber ) {
735815 oldFiber = nextOldFiber ;
736816 }
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+ }
737829 break ;
738830 }
739831 if ( shouldTrackSideEffects ) {
@@ -768,9 +860,14 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
768860 // If we don't have any more existing children we can choose a fast path
769861 // since the rest will all be insertions.
770862 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+ }
771868 const newFiber = createChild (
772869 returnFiber ,
773- step . value ,
870+ newChild ,
774871 priority
775872 ) ;
776873 if ( ! newFiber ) {
@@ -793,11 +890,16 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) {
793890
794891 // Keep scanning and use the map to restore deleted items as moves.
795892 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+ }
796898 const newFiber = updateFromMap (
797899 existingChildren ,
798900 returnFiber ,
799901 newIdx ,
800- step . value ,
902+ newChild ,
801903 priority
802904 ) ;
803905 if ( newFiber ) {
0 commit comments