@@ -788,8 +788,17 @@ function createTask(
788788): Task {
789789 const id = request . nextChunkId ++ ;
790790 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+ }
793802 }
794803 const task : Task = {
795804 id ,
@@ -1251,18 +1260,31 @@ function renderModelDestructive(
12511260 const writtenObjects = request . writtenObjects ;
12521261 const existingId = writtenObjects . get ( value ) ;
12531262 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.
12581272 } else if (modelRoot === value) {
12591273 // This is the ID we're currently emitting so we need to write it
12601274 // once but if we discover it again, we refer to it by id.
12611275 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 ) ;
12621283 } 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 ) ;
12661288 }
12671289 } else {
12681290 // This is the first time we've seen this object. We may never see it again
@@ -1317,7 +1339,18 @@ function renderModelDestructive(
13171339 // $FlowFixMe[method-unbinding]
13181340 if (typeof value.then === 'function') {
13191341 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) {
13211354 // This is the ID we're currently emitting so we need to write it
13221355 // once but if we discover it again, we refer to it by id.
13231356 modelRoot = null ;
@@ -1357,14 +1390,14 @@ function renderModelDestructive(
13571390 }
13581391
13591392 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 ) {
13651394 // This is the ID we're currently emitting so we need to write it
13661395 // once but if we discover it again, we refer to it by id.
13671396 modelRoot = null ;
1397+ } else if (existingId === -1) {
1398+ // Seen but not yet outlined.
1399+ const newId = outlineModel ( request , ( value : any ) ) ;
1400+ return serializeByValueID ( newId ) ;
13681401 } else {
13691402 // We've already emitted this as an outlined object, so we can
13701403 // just refer to that by its existing ID.
@@ -1768,15 +1801,18 @@ function retryTask(request: Request, task: Task): void {
17681801 task . keyPath = null ;
17691802 task . implicitSlot = false ;
17701803
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+ }
17801816 emitModelChunk ( request , task . id , json ) ;
17811817
17821818 request . abortableTasks . delete ( task ) ;
0 commit comments