Skip to content

Commit

Permalink
Add param names into the tree (#38415)
Browse files Browse the repository at this point in the history
- Remove cache value that was incorrectly nested deeper
- Remove extra useEffect (already applied during hydration based on the `useReducer` input)
- Add dynamic parameter name into the tree

Follow-up to #37551, cleans up some code and prepares for catch-all and optional catch-all routes.
  • Loading branch information
timneutkens committed Jul 7, 2022
1 parent 3411794 commit 0299f14
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 54 deletions.
11 changes: 0 additions & 11 deletions packages/next/client/components/app-router.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,6 @@ export default function AppRouter({
}, [])

useEffect(() => {
console.log(
'UPDATE URL',
pushRef.pendingPush ? 'push' : 'replace',
pushRef.mpaNavigation ? 'MPA' : '',
tree
)

if (pushRef.mpaNavigation) {
window.location.href = canonicalUrl
return
Expand Down Expand Up @@ -213,10 +206,6 @@ export default function AppRouter({
}
}, [onPopState])

React.useEffect(() => {
window.history.replaceState({ tree: initialTree }, '')
}, [initialTree])

return (
<PathnameContext.Provider value={pathname}>
<QueryContext.Provider value={query}>
Expand Down
28 changes: 18 additions & 10 deletions packages/next/client/components/layout-router.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import {
FullAppTreeContext,
} from '../../shared/lib/app-router-context'
import { fetchServerResponse } from './app-router.client'
import { matchSegment } from './match-segments'

let infinitePromise: Promise<void> | Error

function equalArray(a: any[], b: any[]) {
return a.length === b.length && a.every((val, i) => val === b[i])
return a.length === b.length && a.every((val, i) => matchSegment(val, b[i]))
}

function pathMatches(
Expand All @@ -29,11 +30,12 @@ function pathMatches(

function createInfinitePromise() {
if (!infinitePromise) {
infinitePromise = new Promise((resolve) => {
setTimeout(() => {
infinitePromise = new Error('Infinite promise')
resolve()
}, 20000)
infinitePromise = new Promise((/* resolve */) => {
// Note: this is used to debug when the rendering is never updated.
// setTimeout(() => {
// infinitePromise = new Error('Infinite promise')
// resolve()
// }, 5000)
})
}

Expand Down Expand Up @@ -68,7 +70,7 @@ export function InnerLayoutRouter({
childNodes.set(path, {
data: null,
subTreeData: childProp.current,
parallelRoutes: new Map([[parallelRouterKey, new Map()]]),
parallelRoutes: new Map(),
})
childProp.current = null
// In the above case childNode was set on childNodes, so we have to get it from the cacheNodes again.
Expand Down Expand Up @@ -128,7 +130,7 @@ export function InnerLayoutRouter({
childNodes.set(path, {
data,
subTreeData: null,
parallelRoutes: new Map([[parallelRouterKey, new Map()]]),
parallelRoutes: new Map(),
})
// In the above case childNode was set on childNodes, so we have to get it from the cacheNodes again.
childNode = childNodes.get(path)
Expand Down Expand Up @@ -243,7 +245,13 @@ export default function OuterLayoutRouter({

// This relates to the segments in the current router
// tree[1].children[0] refers to tree.children.segment in the data format
const currentChildSegment = tree[1][parallelRouterKey][0] ?? childProp.segment
const treeSegment = tree[1][parallelRouterKey][0]
const childPropSegment = Array.isArray(childProp.segment)
? childProp.segment[1]
: childProp.segment
const currentChildSegment =
(Array.isArray(treeSegment) ? treeSegment[1] : treeSegment) ??
childPropSegment
const preservedSegments: string[] = [currentChildSegment]

return (
Expand All @@ -257,7 +265,7 @@ export default function OuterLayoutRouter({
tree={tree}
childNodes={childNodesForParallelRouter!}
childProp={
childProp.segment === preservedSegment ? childProp : null
childPropSegment === preservedSegment ? childProp : null
}
segmentPath={segmentPath}
path={preservedSegment}
Expand Down
20 changes: 20 additions & 0 deletions packages/next/client/components/match-segments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Segment } from '../../server/app-render'

export const matchSegment = (
existingSegment: Segment,
segment: Segment
): boolean => {
// Common case: segment is just a string
if (typeof existingSegment === 'string' && typeof segment === 'string') {
return existingSegment === segment
}

// Dynamic parameter case: segment is an array with param/value. Both param and value are compared.
if (Array.isArray(existingSegment) && Array.isArray(segment)) {
return (
existingSegment[0] === segment[0] && existingSegment[1] === segment[1]
)
}

return false
}
52 changes: 32 additions & 20 deletions packages/next/client/components/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
FlightData,
FlightDataPath,
} from '../../server/app-render'
import { matchSegment } from './match-segments'
import { fetchServerResponse } from './app-router.client'

const fillCacheWithNewSubTreeData = (
Expand All @@ -15,6 +16,8 @@ const fillCacheWithNewSubTreeData = (
const isLastEntry = flightDataPath.length <= 4
const [parallelRouteKey, segment] = flightDataPath

const segmentForCache = Array.isArray(segment) ? segment[1] : segment

const existingChildSegmentMap =
existingCache.parallelRoutes.get(parallelRouteKey)

Expand All @@ -30,8 +33,8 @@ const fillCacheWithNewSubTreeData = (
newCache.parallelRoutes.set(parallelRouteKey, childSegmentMap)
}

const existingChildCacheNode = existingChildSegmentMap.get(segment)
let childCacheNode = childSegmentMap.get(segment)
const existingChildCacheNode = existingChildSegmentMap.get(segmentForCache)
let childCacheNode = childSegmentMap.get(segmentForCache)

// In case of last segment start off the fetch at this level and don't copy further down.
if (isLastEntry) {
Expand All @@ -40,7 +43,7 @@ const fillCacheWithNewSubTreeData = (
!childCacheNode.data ||
childCacheNode === existingChildCacheNode
) {
childSegmentMap.set(segment, {
childSegmentMap.set(segmentForCache, {
data: null,
subTreeData: flightDataPath[3],
parallelRoutes: new Map(),
Expand All @@ -61,7 +64,7 @@ const fillCacheWithNewSubTreeData = (
subTreeData: childCacheNode.subTreeData,
parallelRoutes: new Map(childCacheNode.parallelRoutes),
}
childSegmentMap.set(segment, childCacheNode)
childSegmentMap.set(segmentForCache, childCacheNode)
}

fillCacheWithNewSubTreeData(
Expand Down Expand Up @@ -164,7 +167,7 @@ const createOptimisticTree = (
!flightRouterState || segment !== flightRouterState[0]

let parallelRoutes: FlightRouterState[1] = {}
if (existingSegment === segment) {
if (existingSegment !== null && matchSegment(existingSegment, segment)) {
parallelRoutes = existingParallelRoutes
}

Expand Down Expand Up @@ -210,7 +213,7 @@ const walkTreeWithFlightDataPath = (

// Tree path returned from the server should always match up with the current tree in the browser
// TODO: verify
if (segment !== currentSegment) {
if (!matchSegment(currentSegment, segment)) {
throw new Error('SEGMENT MISMATCH')
}

Expand Down Expand Up @@ -316,6 +319,18 @@ export function reducer(
// The with optimistic tree case only happens when the layouts have a loading state (loading.js)
// The without optimistic tree case happens when there is no loading state, in that case we suspend in this reducer
if (cacheType === 'hard') {
if (
mutable.patchedTree &&
JSON.stringify(mutable.previousTree) === JSON.stringify(state.tree)
) {
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
cache: cache,
tree: mutable.patchedTree,
}
}

// TODO: flag on the tree of which part of the tree for if there is a loading boundary
const isOptimistic = false

Expand All @@ -336,10 +351,14 @@ export function reducer(
cache,
state.cache,
segments.slice(1),
() => fetchServerResponse(url, optimisticTree)
() => {
return fetchServerResponse(url, optimisticTree)
}
)

if (!res?.bailOptimistic) {
mutable.previousTree = state.tree
mutable.patchedTree = optimisticTree
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
Expand All @@ -349,18 +368,6 @@ export function reducer(
}
}

if (
mutable.patchedTree &&
JSON.stringify(mutable.previousTree) === JSON.stringify(state.tree)
) {
return {
canonicalUrl: href,
pushRef: { pendingPush, mpaNavigation: false },
cache: cache,
tree: mutable.patchedTree,
}
}

if (!cache.data) {
cache.data = fetchServerResponse(url, state.tree)
}
Expand Down Expand Up @@ -436,7 +443,12 @@ export function reducer(
const treePath = flightDataPath.slice(0, -3)
const [treePatch] = flightDataPath.slice(-2)

const newTree = walkTreeWithFlightDataPath(treePath, state.tree, treePatch)
const newTree = walkTreeWithFlightDataPath(
// TODO: remove ''
['', ...treePath],
state.tree,
treePatch
)

fillCacheWithNewSubTreeData(cache, state.cache, flightDataPath)

Expand Down
36 changes: 23 additions & 13 deletions packages/next/server/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { tryGetPreviewData } from './api-utils/node'
import { htmlEscapeJsonString } from './htmlescape'
import { stripInternalQueries } from './utils'
import { NextApiRequestCookies } from './api-utils'
import { matchSegment } from '../client/components/match-segments'

const ReactDOMServer = process.env.__NEXT_REACT_ROOT
? require('react-dom/server.browser')
Expand Down Expand Up @@ -245,6 +246,8 @@ function createServerComponentRenderer(
return ServerComponentWrapper
}

export type Segment = string | [param: string, value: string]

type LoaderTree = [
segment: string,
parallelRoutes: { [parallelRouterKey: string]: LoaderTree },
Expand All @@ -256,7 +259,7 @@ type LoaderTree = [
]

export type FlightRouterState = [
segment: string,
segment: Segment,
parallelRoutes: { [parallelRouterKey: string]: FlightRouterState },
url?: string,
refresh?: 'refetch'
Expand All @@ -266,30 +269,33 @@ export type FlightSegmentPath =
| any[]
// Looks somewhat like this
| [
segment: string,
segment: Segment,
parallelRouterKey: string,
segment: string,
segment: Segment,
parallelRouterKey: string,
segment: string,
segment: Segment,
parallelRouterKey: string
]

export type FlightDataPath =
| any[]
// Looks somewhat like this
| [
segment: string,
segment: Segment,
parallelRoute: string,
segment: string,
segment: Segment,
parallelRoute: string,
segment: string,
segment: Segment,
parallelRoute: string,
tree: FlightRouterState,
subTreeData: React.ReactNode
]

export type FlightData = Array<FlightDataPath> | string
export type ChildProp = { current: React.ReactNode; segment: string }
export type ChildProp = {
current: React.ReactNode
segment: Segment
}

export async function renderToHTML(
req: IncomingMessage,
Expand Down Expand Up @@ -395,7 +401,7 @@ export async function renderToHTML(
const dynamicParam = getDynamicParamFromSegment(segment)

const segmentTree: FlightRouterState = [
dynamicParam ? dynamicParam.value : segment,
dynamicParam ? [dynamicParam.param, dynamicParam.value] : segment,
{},
]

Expand Down Expand Up @@ -459,7 +465,9 @@ export async function renderToHTML(
}
: parentParams

const actualSegment = segmentParam ? segmentParam.value : segment
const actualSegment = segmentParam
? [segmentParam.param, segmentParam.value]
: segment

// This happens outside of rendering in order to eagerly kick off data fetching for layouts / the page further down
const parallelRouteComponents = Object.keys(parallelRoutes).reduce(
Expand All @@ -482,7 +490,7 @@ export async function renderToHTML(
const childProp: ChildProp = {
current: <ChildComponent />,
segment: childSegmentParam
? childSegmentParam.value
? [childSegmentParam.param, childSegmentParam.value]
: parallelRoutes[currentValue][0],
}

Expand Down Expand Up @@ -621,7 +629,9 @@ export async function renderToHTML(
const parallelRoutesKeys = Object.keys(parallelRoutes)

const segmentParam = getDynamicParamFromSegment(segment)
const actualSegment = segmentParam ? segmentParam.value : segment
const actualSegment: Segment = segmentParam
? [segmentParam.param, segmentParam.value]
: segment

const currentParams = segmentParam
? {
Expand All @@ -632,7 +642,7 @@ export async function renderToHTML(

const renderComponentsOnThisLevel =
!flightRouterState ||
actualSegment !== flightRouterState[0] ||
!matchSegment(actualSegment, flightRouterState[0]) ||
// Last item in the tree
parallelRoutesKeys.length === 0 ||
// Explicit refresh
Expand Down

0 comments on commit 0299f14

Please sign in to comment.