@@ -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,30 @@ 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 {
12631284 // We've already emitted this as an outlined object, so we can
12641285 // just refer to that by its existing ID.
1265- return serializeByValueID ( existingId ) ;
1286+ return serializeLazyID ( existingId ) ;
12661287 }
12671288 } else {
12681289 // This is the first time we've seen this object. We may never see it again
@@ -1317,7 +1338,18 @@ function renderModelDestructive(
13171338 // $FlowFixMe[method-unbinding]
13181339 if (typeof value.then === 'function') {
13191340 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) {
13211353 // This is the ID we're currently emitting so we need to write it
13221354 // once but if we discover it again, we refer to it by id.
13231355 modelRoot = null ;
@@ -1357,14 +1389,14 @@ function renderModelDestructive(
13571389 }
13581390
13591391 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 ) {
13651393 // This is the ID we're currently emitting so we need to write it
13661394 // once but if we discover it again, we refer to it by id.
13671395 modelRoot = null ;
1396+ } else if (existingId === -1) {
1397+ // Seen but not yet outlined.
1398+ const newId = outlineModel ( request , ( value : any ) ) ;
1399+ return serializeByValueID ( newId ) ;
13681400 } else {
13691401 // We've already emitted this as an outlined object, so we can
13701402 // just refer to that by its existing ID.
@@ -1768,15 +1800,18 @@ function retryTask(request: Request, task: Task): void {
17681800 task . keyPath = null ;
17691801 task . implicitSlot = false ;
17701802
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+ }
17801815 emitModelChunk ( request , task . id , json ) ;
17811816
17821817 request . abortableTasks . delete ( task ) ;
0 commit comments