Skip to content

Commit b017261

Browse files
Inline ChildProp (#58519)
I'm working on a refactor to seed the CacheNodes as soon as the Flight payload is received, rather than lazily during the render phase. This means we no longer need to pass a child element prop to LayoutRouter via childProp. ChildProp includes two fields: a segment and a child element. The child element is the part that will soon be removed, because we'll instead always read from the cache nodes. But even after this refactor, we still need to pass the segment to LayoutRouter. So as an incremental step, I've inlined both fields into separate props: - childProp.current -> initialChildNode. This will be removed in a later step in favor of reading from the cache nodes. In fact, we already always read from the cache nodes — childProp is ignored completely once the cache node is populated, hence the updated name. - childProp.segment -> childPropSegment. This probably isn't the best name anymore but I'll leave renaming until later once more of this refactor has settled. --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent c26c771 commit b017261

File tree

3 files changed

+57
-69
lines changed

3 files changed

+57
-69
lines changed

packages/next/src/client/components/layout-router.tsx

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { ChildSegmentMap } from '../../shared/lib/app-router-context.shared
44
import type {
55
FlightRouterState,
66
FlightSegmentPath,
7-
ChildProp,
87
Segment,
98
} from '../../server/app-render/types'
109
import type { ErrorComponent } from './error-boundary'
@@ -315,7 +314,7 @@ function InnerLayoutRouter({
315314
parallelRouterKey,
316315
url,
317316
childNodes,
318-
childProp,
317+
initialChildNode,
319318
segmentPath,
320319
tree,
321320
// TODO-APP: implement `<Offscreen>` when available.
@@ -325,7 +324,7 @@ function InnerLayoutRouter({
325324
parallelRouterKey: string
326325
url: string
327326
childNodes: ChildSegmentMap
328-
childProp: ChildProp | null
327+
initialChildNode: React.ReactNode | null
329328
segmentPath: FlightSegmentPath
330329
tree: FlightRouterState
331330
isActive: boolean
@@ -341,19 +340,25 @@ function InnerLayoutRouter({
341340
// Read segment path from the parallel router cache node.
342341
let childNode = childNodes.get(cacheKey)
343342

344-
// If childProp is available this means it's the Flight / SSR case.
345-
if (
346-
childProp &&
347-
// TODO-APP: verify if this can be null based on user code
348-
childProp.current !== null
349-
) {
343+
// If initialChildNode is available this means it's the Flight / SSR case.
344+
// TODO: `null` is a valid React Node, so technically we should use some other
345+
// value besides `null` to indicate that the tree is partial. However, we're
346+
// about to remove all the cases that lead to a partial tree, so this soon
347+
// won't be an issue.
348+
if (initialChildNode !== null) {
350349
if (!childNode) {
351350
// Add the segment's subTreeData to the cache.
352351
// This writes to the cache when there is no item in the cache yet. It never *overwrites* existing cache items which is why it's safe in concurrent mode.
352+
353+
// TODO: We should seed all the CacheNodes as soon as the Flight payload
354+
// is received. We already collect them eagerly on the server, so we
355+
// shouldn't need to wait until the render phase to write them into
356+
// the cache. Requires refactoring the Flight response type. Then we can
357+
// delete this code.
353358
childNode = {
354359
status: CacheStates.READY,
355360
data: null,
356-
subTreeData: childProp.current,
361+
subTreeData: initialChildNode,
357362
parallelRoutes: new Map(),
358363
}
359364

@@ -363,7 +368,7 @@ function InnerLayoutRouter({
363368
// @ts-expect-error we're changing it's type!
364369
childNode.status = CacheStates.READY
365370
// @ts-expect-error
366-
childNode.subTreeData = childProp.current
371+
childNode.subTreeData = initialChildNode
367372
}
368373
}
369374
}
@@ -498,7 +503,8 @@ function LoadingBoundary({
498503
export default function OuterLayoutRouter({
499504
parallelRouterKey,
500505
segmentPath,
501-
childProp,
506+
initialChildNode,
507+
childPropSegment,
502508
error,
503509
errorStyles,
504510
errorScripts,
@@ -515,7 +521,8 @@ export default function OuterLayoutRouter({
515521
}: {
516522
parallelRouterKey: string
517523
segmentPath: FlightSegmentPath
518-
childProp: ChildProp
524+
initialChildNode: React.ReactNode | null
525+
childPropSegment: Segment
519526
error: ErrorComponent
520527
errorStyles: React.ReactNode | undefined
521528
errorScripts: React.ReactNode | undefined
@@ -550,8 +557,6 @@ export default function OuterLayoutRouter({
550557
// The reason arrays are used in the data format is that these are transferred from the server to the browser so it's optimized to save bytes.
551558
const treeSegment = tree[1][parallelRouterKey][0]
552559

553-
const childPropSegment = childProp.segment
554-
555560
// If segment is an array it's a dynamic route and we want to read the dynamic route value as the segment to get from the cache.
556561
const currentChildSegmentValue = getSegmentValue(treeSegment)
557562

@@ -607,7 +612,9 @@ export default function OuterLayoutRouter({
607612
url={url}
608613
tree={tree}
609614
childNodes={childNodesForParallelRouter!}
610-
childProp={isChildPropSegment ? childProp : null}
615+
initialChildNode={
616+
isChildPropSegment ? initialChildNode : null
617+
}
611618
segmentPath={segmentPath}
612619
cacheKey={cacheKey}
613620
isActive={

packages/next/src/server/app-render/create-component-tree.tsx

Lines changed: 34 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ChildProp, FlightSegmentPath } from './types'
1+
import type { FlightSegmentPath } from './types'
22
import React from 'react'
33
import { isClientReference } from '../../lib/client-reference'
44
import { getLayoutOrPageModule } from '../lib/app-dir-module'
@@ -301,44 +301,11 @@ export async function createComponentTree({
301301
const notFoundComponent =
302302
NotFound && isChildrenRouteKey ? <NotFound /> : undefined
303303

304-
function getParallelRoutePair(
305-
currentChildProp: ChildProp,
306-
currentStyles: React.ReactNode
307-
): [string, React.ReactNode] {
308-
// This is turned back into an object below.
309-
return [
310-
parallelRouteKey,
311-
<LayoutRouter
312-
parallelRouterKey={parallelRouteKey}
313-
segmentPath={createSegmentPath(currentSegmentPath)}
314-
loading={Loading ? <Loading /> : undefined}
315-
loadingStyles={loadingStyles}
316-
loadingScripts={loadingScripts}
317-
// TODO-APP: Add test for loading returning `undefined`. This currently can't be tested as the `webdriver()` tab will wait for the full page to load before returning.
318-
hasLoading={Boolean(Loading)}
319-
error={ErrorComponent}
320-
errorStyles={errorStyles}
321-
errorScripts={errorScripts}
322-
template={
323-
<Template>
324-
<RenderFromTemplateContext />
325-
</Template>
326-
}
327-
templateStyles={templateStyles}
328-
templateScripts={templateScripts}
329-
notFound={notFoundComponent}
330-
notFoundStyles={notFoundStyles}
331-
childProp={currentChildProp}
332-
styles={currentStyles}
333-
/>,
334-
]
335-
}
336-
337304
// if we're prefetching and that there's a Loading component, we bail out
338305
// otherwise we keep rendering for the prefetch.
339306
// We also want to bail out if there's no Loading component in the tree.
340307
let currentStyles = undefined
341-
let childElement = null
308+
let initialChildNode = null
342309
const childPropSegment = addSearchParamsIfPageSegment(
343310
childSegmentParam ? childSegmentParam.treeSegment : childSegment,
344311
query
@@ -367,15 +334,40 @@ export async function createComponentTree({
367334
})
368335

369336
currentStyles = childComponentStyles
370-
childElement = <ChildComponent />
371-
}
372-
373-
const childProp: ChildProp = {
374-
current: childElement,
375-
segment: childPropSegment,
337+
initialChildNode = <ChildComponent />
376338
}
377339

378-
return getParallelRoutePair(childProp, currentStyles)
340+
// This is turned back into an object below.
341+
return [
342+
parallelRouteKey,
343+
<LayoutRouter
344+
parallelRouterKey={parallelRouteKey}
345+
segmentPath={createSegmentPath(currentSegmentPath)}
346+
loading={Loading ? <Loading /> : undefined}
347+
loadingStyles={loadingStyles}
348+
loadingScripts={loadingScripts}
349+
// TODO-APP: Add test for loading returning `undefined`. This currently can't be tested as the `webdriver()` tab will wait for the full page to load before returning.
350+
hasLoading={Boolean(Loading)}
351+
error={ErrorComponent}
352+
errorStyles={errorStyles}
353+
errorScripts={errorScripts}
354+
template={
355+
<Template>
356+
<RenderFromTemplateContext />
357+
</Template>
358+
}
359+
templateStyles={templateStyles}
360+
templateScripts={templateScripts}
361+
notFound={notFoundComponent}
362+
notFoundStyles={notFoundStyles}
363+
// TODO: This prop will soon by removed and instead we'll return all
364+
// the child nodes in the entire tree at the top level of the
365+
// Flight response.
366+
initialChildNode={initialChildNode}
367+
childPropSegment={childPropSegment}
368+
styles={currentStyles}
369+
/>,
370+
]
379371
}
380372
)
381373
)

packages/next/src/server/app-render/types.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,6 @@ export type ActionFlightResponse =
9191
// This case happens when `redirect()` is called in a server action.
9292
| NextFlightResponse
9393

94-
/**
95-
* Property holding the current subTreeData.
96-
*/
97-
export type ChildProp = {
98-
/**
99-
* Null indicates that the tree is partial
100-
*/
101-
current: React.ReactNode | null
102-
segment: Segment
103-
}
104-
10594
export interface RenderOptsPartial {
10695
err?: Error | null
10796
dev?: boolean

0 commit comments

Comments
 (0)