@@ -788,8 +788,17 @@ function createTask(
788
788
): Task {
789
789
const id = request . nextChunkId ++ ;
790
790
if ( typeof model === 'object' && model !== null ) {
791
- // Register this model as having the ID we're about to write.
792
- request . writtenObjects . set ( model , id ) ;
791
+ // If we're about to write this into a new task we can assign it an ID early so that
792
+ // any other references can refer to the value we're about to write.
793
+ if (
794
+ enableServerComponentKeys &&
795
+ ( keyPath !== null || implicitSlot || context !== rootContextSnapshot )
796
+ ) {
797
+ // If we're in some kind of context we can't necessarily reuse this object depending
798
+ // what parent components are used.
799
+ } else {
800
+ request . writtenObjects . set ( model , id ) ;
801
+ }
793
802
}
794
803
const task : Task = {
795
804
id ,
@@ -1251,18 +1260,30 @@ function renderModelDestructive(
1251
1260
const writtenObjects = request . writtenObjects ;
1252
1261
const existingId = writtenObjects . get ( value ) ;
1253
1262
if ( existingId !== undefined ) {
1254
- if ( existingId === - 1 ) {
1255
- // Seen but not yet outlined.
1256
- const newId = outlineModel ( request , value ) ;
1257
- return serializeByValueID ( newId ) ;
1263
+ if (
1264
+ enableServerComponentKeys &&
1265
+ ( task . keyPath !== null ||
1266
+ task . implicitSlot ||
1267
+ task . context !== rootContextSnapshot )
1268
+ ) {
1269
+ // If we're in some kind of context we can't reuse the result of this render or
1270
+ // previous renders of this element. We only reuse elements if they're not wrapped
1271
+ // by another Server Component.
1258
1272
} else if (modelRoot === value) {
1259
1273
// This is the ID we're currently emitting so we need to write it
1260
1274
// once but if we discover it again, we refer to it by id.
1261
1275
modelRoot = null ;
1276
+ } else if (existingId === -1) {
1277
+ // Seen but not yet outlined.
1278
+ // TODO: If we throw here we can treat this as suspending which causes an outline
1279
+ // but that is able to reuse the same task if we're already in one but then that
1280
+ // will be a lazy future value rather than guaranteed to exist but maybe that's good.
1281
+ const newId = outlineModel ( request , ( value : any ) ) ;
1282
+ return serializeLazyID ( newId ) ;
1262
1283
} else {
1263
1284
// We've already emitted this as an outlined object, so we can
1264
1285
// just refer to that by its existing ID.
1265
- return serializeByValueID ( existingId ) ;
1286
+ return serializeLazyID ( existingId ) ;
1266
1287
}
1267
1288
} else {
1268
1289
// This is the first time we've seen this object. We may never see it again
@@ -1317,7 +1338,18 @@ function renderModelDestructive(
1317
1338
// $FlowFixMe[method-unbinding]
1318
1339
if (typeof value.then === 'function') {
1319
1340
if ( existingId !== undefined ) {
1320
- if ( modelRoot === value ) {
1341
+ if (
1342
+ enableServerComponentKeys &&
1343
+ ( task . keyPath !== null ||
1344
+ task . implicitSlot ||
1345
+ task . context !== rootContextSnapshot )
1346
+ ) {
1347
+ // If we're in some kind of context we can't reuse the result of this render or
1348
+ // previous renders of this element. We only reuse Promises if they're not wrapped
1349
+ // by another Server Component.
1350
+ const promiseId = serializeThenable ( request , task , ( value : any ) ) ;
1351
+ return serializePromiseID ( promiseId ) ;
1352
+ } else if (modelRoot === value) {
1321
1353
// This is the ID we're currently emitting so we need to write it
1322
1354
// once but if we discover it again, we refer to it by id.
1323
1355
modelRoot = null ;
@@ -1357,14 +1389,14 @@ function renderModelDestructive(
1357
1389
}
1358
1390
1359
1391
if ( existingId !== undefined ) {
1360
- if ( existingId === - 1 ) {
1361
- // Seen but not yet outlined.
1362
- const newId = outlineModel ( request , value ) ;
1363
- return serializeByValueID ( newId ) ;
1364
- } else if (modelRoot === value) {
1392
+ if ( modelRoot === value ) {
1365
1393
// This is the ID we're currently emitting so we need to write it
1366
1394
// once but if we discover it again, we refer to it by id.
1367
1395
modelRoot = null ;
1396
+ } else if (existingId === -1) {
1397
+ // Seen but not yet outlined.
1398
+ const newId = outlineModel ( request , ( value : any ) ) ;
1399
+ return serializeByValueID ( newId ) ;
1368
1400
} else {
1369
1401
// We've already emitted this as an outlined object, so we can
1370
1402
// just refer to that by its existing ID.
@@ -1768,15 +1800,18 @@ function retryTask(request: Request, task: Task): void {
1768
1800
task . keyPath = null ;
1769
1801
task . implicitSlot = false ;
1770
1802
1771
- // If the value is a string, it means it's a terminal value adn we already escaped it
1772
- // We don't need to escape it again so it's not passed the toJSON replacer.
1773
- // Object might contain unresolved values like additional elements.
1774
- // This is simulating what the JSON loop would do if this was part of it.
1775
- // $FlowFixMe[incompatible-type] stringify can return null
1776
- const json : string =
1777
- typeof resolvedModel === 'string'
1778
- ? stringify ( resolvedModel )
1779
- : stringify ( resolvedModel , task . toJSON ) ;
1803
+ let json : string ;
1804
+ if ( typeof resolvedModel === 'object' && resolvedModel !== null ) {
1805
+ // Object might contain unresolved values like additional elements.
1806
+ // This is simulating what the JSON loop would do if this was part of it.
1807
+ // $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
1808
+ json = stringify ( resolvedModel , task . toJSON ) ;
1809
+ } else {
1810
+ // If the value is a string, it means it's a terminal value and we already escaped it
1811
+ // We don't need to escape it again so it's not passed the toJSON replacer.
1812
+ // $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
1813
+ json = stringify ( resolvedModel ) ;
1814
+ }
1780
1815
emitModelChunk ( request , task . id , json ) ;
1781
1816
1782
1817
request . abortableTasks . delete ( task ) ;
0 commit comments