@@ -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,31 @@ 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
- // We've already emitted this as an outlined object, so we can
1264
- // just refer to that by its existing ID.
1265
- return serializeByValueID ( existingId ) ;
1284
+ // We've already emitted this as an outlined object, so we can refer to that by its
1285
+ // existing ID. We use a lazy reference since, unlike plain objects, elements might
1286
+ // suspend so it might not have emitted yet even if we have the ID for it.
1287
+ return serializeLazyID ( existingId ) ;
1266
1288
}
1267
1289
} else {
1268
1290
// This is the first time we've seen this object. We may never see it again
@@ -1317,7 +1339,18 @@ function renderModelDestructive(
1317
1339
// $FlowFixMe[method-unbinding]
1318
1340
if (typeof value.then === 'function') {
1319
1341
if ( existingId !== undefined ) {
1320
- if ( modelRoot === value ) {
1342
+ if (
1343
+ enableServerComponentKeys &&
1344
+ ( task . keyPath !== null ||
1345
+ task . implicitSlot ||
1346
+ task . context !== rootContextSnapshot )
1347
+ ) {
1348
+ // If we're in some kind of context we can't reuse the result of this render or
1349
+ // previous renders of this element. We only reuse Promises if they're not wrapped
1350
+ // by another Server Component.
1351
+ const promiseId = serializeThenable ( request , task , ( value : any ) ) ;
1352
+ return serializePromiseID ( promiseId ) ;
1353
+ } else if (modelRoot === value) {
1321
1354
// This is the ID we're currently emitting so we need to write it
1322
1355
// once but if we discover it again, we refer to it by id.
1323
1356
modelRoot = null ;
@@ -1357,14 +1390,14 @@ function renderModelDestructive(
1357
1390
}
1358
1391
1359
1392
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) {
1393
+ if ( modelRoot === value ) {
1365
1394
// This is the ID we're currently emitting so we need to write it
1366
1395
// once but if we discover it again, we refer to it by id.
1367
1396
modelRoot = null ;
1397
+ } else if (existingId === -1) {
1398
+ // Seen but not yet outlined.
1399
+ const newId = outlineModel ( request , ( value : any ) ) ;
1400
+ return serializeByValueID ( newId ) ;
1368
1401
} else {
1369
1402
// We've already emitted this as an outlined object, so we can
1370
1403
// just refer to that by its existing ID.
@@ -1768,15 +1801,18 @@ function retryTask(request: Request, task: Task): void {
1768
1801
task . keyPath = null ;
1769
1802
task . implicitSlot = false ;
1770
1803
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 ) ;
1804
+ let json : string ;
1805
+ if ( typeof resolvedModel === 'object' && resolvedModel !== null ) {
1806
+ // Object might contain unresolved values like additional elements.
1807
+ // This is simulating what the JSON loop would do if this was part of it.
1808
+ // $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
1809
+ json = stringify ( resolvedModel , task . toJSON ) ;
1810
+ } else {
1811
+ // If the value is a string, it means it's a terminal value and we already escaped it
1812
+ // We don't need to escape it again so it's not passed the toJSON replacer.
1813
+ // $FlowFixMe[incompatible-type] stringify can return null for undefined but we never do
1814
+ json = stringify ( resolvedModel ) ;
1815
+ }
1780
1816
emitModelChunk ( request , task . id , json ) ;
1781
1817
1782
1818
request . abortableTasks . delete ( task ) ;
0 commit comments