@@ -399,6 +399,7 @@ export type Request = {
399
399
onPostpone : ( reason : string ) => void ,
400
400
// DEV-only
401
401
environmentName : string ,
402
+ didWarnForKey : null | WeakSet < ReactComponentInfo > ,
402
403
} ;
403
404
404
405
const {
@@ -500,6 +501,7 @@ export function createRequest(
500
501
if ( __DEV__ ) {
501
502
request . environmentName =
502
503
environmentName === undefined ? 'Server' : environmentName ;
504
+ request . didWarnForKey = null ;
503
505
}
504
506
const rootTask = createTask ( request , model , null , false , abortSet ) ;
505
507
pingedTasks . push ( rootTask ) ;
@@ -965,6 +967,7 @@ function renderFunctionComponent<Props>(
965
967
props : Props ,
966
968
owner : null | ReactComponentInfo , // DEV-only
967
969
stack : null | string , // DEV-only
970
+ validated : number , // DEV-only
968
971
) : ReactJSONValue {
969
972
// Reset the task's thenable state before continuing, so that if a later
970
973
// component suspends we can reuse the same task object. If the same
@@ -1005,6 +1008,10 @@ function renderFunctionComponent<Props>(
1005
1008
// being no references to this as an owner.
1006
1009
outlineModel ( request , componentDebugInfo ) ;
1007
1010
emitDebugChunk ( request , componentDebugID , componentDebugInfo ) ;
1011
+
1012
+ if ( enableOwnerStacks ) {
1013
+ warnForMissingKey ( request , key , validated , componentDebugInfo ) ;
1014
+ }
1008
1015
}
1009
1016
prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);
1010
1017
result = callComponentInDEV(Component, props, componentDebugInfo);
@@ -1102,6 +1109,8 @@ function renderFunctionComponent<Props>(
1102
1109
if ( __DEV__ ) {
1103
1110
( result : any ) . _debugInfo = iterableChild . _debugInfo ;
1104
1111
}
1112
+ } else if ( __DEV__ && ( result : any ) . $$typeof === REACT_ELEMENT_TYPE ) {
1113
+ ( result : any ) . _store . validated = 1 ;
1105
1114
}
1106
1115
}
1107
1116
// Track this element's key on the Server Component on the keyPath context..
@@ -1124,11 +1133,68 @@ function renderFunctionComponent<Props>(
1124
1133
return json;
1125
1134
}
1126
1135
1136
+ function warnForMissingKey (
1137
+ request : Request ,
1138
+ key : null | string ,
1139
+ validated : number ,
1140
+ componentDebugInfo : ReactComponentInfo ,
1141
+ ) : void {
1142
+ if ( __DEV__ ) {
1143
+ if ( validated !== 2 ) {
1144
+ return ;
1145
+ }
1146
+
1147
+ let didWarnForKey = request.didWarnForKey;
1148
+ if (didWarnForKey == null) {
1149
+ didWarnForKey = request . didWarnForKey = new WeakSet ( ) ;
1150
+ }
1151
+ const parentOwner = componentDebugInfo.owner;
1152
+ if (parentOwner != null) {
1153
+ if ( didWarnForKey . has ( parentOwner ) ) {
1154
+ // We already warned for other children in this parent.
1155
+ return ;
1156
+ }
1157
+ didWarnForKey.add(parentOwner);
1158
+ }
1159
+
1160
+ // Call with the server component as the currently rendering component
1161
+ // for context.
1162
+ callComponentInDEV (
1163
+ ( ) => {
1164
+ console . error (
1165
+ 'Each child in a list should have a unique "key" prop.' +
1166
+ '%s%s See https://react.dev/link/warning-keys for more information.' ,
1167
+ '' ,
1168
+ '' ,
1169
+ ) ;
1170
+ } ,
1171
+ null,
1172
+ componentDebugInfo,
1173
+ );
1174
+ }
1175
+ }
1176
+
1127
1177
function renderFragment (
1128
1178
request : Request ,
1129
1179
task : Task ,
1130
1180
children : $ReadOnlyArray < ReactClientValue > ,
1131
1181
): ReactJSONValue {
1182
+ if ( __DEV__ ) {
1183
+ for ( let i = 0 ; i < children . length ; i ++ ) {
1184
+ const child = children [ i ] ;
1185
+ if (
1186
+ child !== null &&
1187
+ typeof child === 'object' &&
1188
+ child . $$typeof === REACT_ELEMENT_TYPE
1189
+ ) {
1190
+ const element : ReactElement = ( child : any ) ;
1191
+ if ( element . key === null && ! element . _store . validated ) {
1192
+ element . _store . validated = 2 ;
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+
1132
1198
if ( task . keyPath !== null ) {
1133
1199
// We have a Server Component that specifies a key but we're now splitting
1134
1200
// the tree using a fragment.
@@ -1231,6 +1297,7 @@ function renderClientElement(
1231
1297
props : any ,
1232
1298
owner : null | ReactComponentInfo , // DEV-only
1233
1299
stack : null | string , // DEV-only
1300
+ validated : number , // DEV-only
1234
1301
) : ReactJSONValue {
1235
1302
// We prepend the terminal client element that actually gets serialized with
1236
1303
// the keys of any Server Components which are not serialized.
@@ -1242,7 +1309,7 @@ function renderClientElement(
1242
1309
}
1243
1310
const element = __DEV__
1244
1311
? enableOwnerStacks
1245
- ? [ REACT_ELEMENT_TYPE , type , key , props , owner , stack ]
1312
+ ? [ REACT_ELEMENT_TYPE , type , key , props , owner , stack , validated ]
1246
1313
: [ REACT_ELEMENT_TYPE , type , key , props , owner ]
1247
1314
: [ REACT_ELEMENT_TYPE , type , key , props ] ;
1248
1315
if ( task . implicitSlot && key !== null ) {
@@ -1292,6 +1359,7 @@ function renderElement(
1292
1359
props : any ,
1293
1360
owner : null | ReactComponentInfo , // DEV only
1294
1361
stack : null | string , // DEV only
1362
+ validated : number , // DEV only
1295
1363
) : ReactJSONValue {
1296
1364
if ( ref !== null && ref !== undefined ) {
1297
1365
// When the ref moves to the regular props object this will implicitly
@@ -1312,7 +1380,15 @@ function renderElement(
1312
1380
if ( typeof type === 'function' ) {
1313
1381
if ( isClientReference ( type ) || isOpaqueTemporaryReference ( type ) ) {
1314
1382
// This is a reference to a Client Component.
1315
- return renderClientElement ( task , type , key , props , owner , stack ) ;
1383
+ return renderClientElement (
1384
+ task ,
1385
+ type ,
1386
+ key ,
1387
+ props ,
1388
+ owner ,
1389
+ stack ,
1390
+ validated ,
1391
+ ) ;
1316
1392
}
1317
1393
// This is a Server Component.
1318
1394
return renderFunctionComponent (
@@ -1323,10 +1399,11 @@ function renderElement(
1323
1399
props ,
1324
1400
owner ,
1325
1401
stack ,
1402
+ validated ,
1326
1403
) ;
1327
1404
} else if ( typeof type === 'string' ) {
1328
1405
// This is a host element. E.g. HTML.
1329
- return renderClientElement ( task , type , key , props , owner , stack ) ;
1406
+ return renderClientElement ( task , type , key , props , owner , stack , validated ) ;
1330
1407
} else if ( typeof type === 'symbol' ) {
1331
1408
if ( type === REACT_FRAGMENT_TYPE && key === null ) {
1332
1409
// For key-less fragments, we add a small optimization to avoid serializing
@@ -1347,11 +1424,19 @@ function renderElement(
1347
1424
}
1348
1425
// This might be a built-in React component. We'll let the client decide.
1349
1426
// Any built-in works as long as its props are serializable.
1350
- return renderClientElement ( task , type , key , props , owner , stack ) ;
1427
+ return renderClientElement ( task , type , key , props , owner , stack , validated ) ;
1351
1428
} else if ( type != null && typeof type === 'object' ) {
1352
1429
if ( isClientReference ( type ) ) {
1353
1430
// This is a reference to a Client Component.
1354
- return renderClientElement ( task , type , key , props , owner , stack ) ;
1431
+ return renderClientElement (
1432
+ task ,
1433
+ type ,
1434
+ key ,
1435
+ props ,
1436
+ owner ,
1437
+ stack ,
1438
+ validated ,
1439
+ ) ;
1355
1440
}
1356
1441
switch ( type . $$typeof ) {
1357
1442
case REACT_LAZY_TYPE : {
@@ -1372,6 +1457,7 @@ function renderElement(
1372
1457
props ,
1373
1458
owner ,
1374
1459
stack ,
1460
+ validated ,
1375
1461
) ;
1376
1462
}
1377
1463
case REACT_FORWARD_REF_TYPE : {
@@ -1383,6 +1469,7 @@ function renderElement(
1383
1469
props ,
1384
1470
owner ,
1385
1471
stack ,
1472
+ validated ,
1386
1473
) ;
1387
1474
}
1388
1475
case REACT_MEMO_TYPE : {
@@ -1395,6 +1482,7 @@ function renderElement(
1395
1482
props ,
1396
1483
owner ,
1397
1484
stack ,
1485
+ validated ,
1398
1486
) ;
1399
1487
}
1400
1488
}
@@ -1963,8 +2051,11 @@ function renderModelDestructive(
1963
2051
props ,
1964
2052
__DEV__ ? element . _owner : null ,
1965
2053
__DEV__ && enableOwnerStacks
1966
- ? filterDebugStack ( element . _debugStack )
2054
+ ? ! element . _debugStack || typeof element . _debugStack === 'string'
2055
+ ? element . _debugStack
2056
+ : filterDebugStack ( element . _debugStack )
1967
2057
: null ,
2058
+ __DEV__ && enableOwnerStacks ? element . _store . validated : 0 ,
1968
2059
) ;
1969
2060
}
1970
2061
case REACT_LAZY_TYPE : {
0 commit comments