Remove mutable
field from action types (#59221)
Similar in spirit to #58938. The app router reducer state used to be managed by useReducer, so it was written to be resilient to rebasing — the same action may be processed multiple times. Now that we've lifted the reducer outside of React (#56497), each action runs only a single time. So we can simplify some of the logic. The purpose of the `mutable` field was so that if an action is processed multiple times, state could be reused between each run; for example, to prevent redundant network fetches. Now that this scenario can no longer happen, we can remove it. I had to update some of the unit tests in navigate-reducer because they were written with the assumption that the reducer was called multiple times. As far as I can tell, most of this behavior is covered by e2e tests anyway, so I don't think it's too risky. Closes NEXT-1782
This commit is contained in:
parent
b2e183ec1d
commit
65a74b1bef
11 changed files with 120 additions and 410 deletions
|
@ -162,7 +162,6 @@ function useServerActionDispatcher(dispatch: React.Dispatch<ReducerActions>) {
|
|||
dispatch({
|
||||
...actionPayload,
|
||||
type: ACTION_SERVER_ACTION,
|
||||
mutable: {},
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -189,7 +188,6 @@ function useChangeByServerResponse(
|
|||
flightData,
|
||||
previousTree,
|
||||
overrideCanonicalUrl,
|
||||
mutable: {},
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -209,7 +207,6 @@ function useNavigate(dispatch: React.Dispatch<ReducerActions>): RouterNavigate {
|
|||
locationSearch: location.search,
|
||||
shouldScroll: shouldScroll ?? true,
|
||||
navigateType,
|
||||
mutable: {},
|
||||
})
|
||||
},
|
||||
[dispatch]
|
||||
|
@ -329,7 +326,6 @@ function Router({
|
|||
startTransition(() => {
|
||||
dispatch({
|
||||
type: ACTION_REFRESH,
|
||||
mutable: {},
|
||||
origin: window.location.origin,
|
||||
})
|
||||
})
|
||||
|
@ -344,7 +340,6 @@ function Router({
|
|||
startTransition(() => {
|
||||
dispatch({
|
||||
type: ACTION_FAST_REFRESH,
|
||||
mutable: {},
|
||||
origin: window.location.origin,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import type {
|
|||
ReadonlyReducerState,
|
||||
ReducerState,
|
||||
FastRefreshAction,
|
||||
Mutable,
|
||||
} from '../router-reducer-types'
|
||||
import { handleExternalUrl } from './navigate-reducer'
|
||||
import { handleMutable } from '../handle-mutable'
|
||||
|
@ -18,16 +19,10 @@ function fastRefreshReducerImpl(
|
|||
state: ReadonlyReducerState,
|
||||
action: FastRefreshAction
|
||||
): ReducerState {
|
||||
const { mutable, origin } = action
|
||||
const { origin } = action
|
||||
const mutable: Mutable = {}
|
||||
const href = state.canonicalUrl
|
||||
|
||||
const isForCurrentTree =
|
||||
JSON.stringify(mutable.previousTree) === JSON.stringify(state.tree)
|
||||
|
||||
if (isForCurrentTree) {
|
||||
return handleMutable(state, mutable)
|
||||
}
|
||||
|
||||
mutable.preserveCustomHistoryState = false
|
||||
|
||||
const cache: CacheNode = createEmptyCacheNode()
|
||||
|
@ -102,7 +97,6 @@ function fastRefreshReducerImpl(
|
|||
currentCache = cache
|
||||
}
|
||||
|
||||
mutable.previousTree = currentTree
|
||||
mutable.patchedTree = newTree
|
||||
mutable.canonicalUrl = href
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
import type { NavigateAction, PrefetchAction } from '../router-reducer-types'
|
||||
import { navigateReducer } from './navigate-reducer'
|
||||
import { prefetchReducer } from './prefetch-reducer'
|
||||
import { handleMutable } from '../handle-mutable'
|
||||
|
||||
const buildId = 'development'
|
||||
|
||||
|
@ -106,19 +107,6 @@ const getInitialRouterStateTree = (): FlightRouterState => [
|
|||
true,
|
||||
]
|
||||
|
||||
async function runPromiseThrowChain(fn: any): Promise<any> {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (err) {
|
||||
if (err instanceof Promise) {
|
||||
await err
|
||||
return await runPromiseThrowChain(fn)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
describe('navigateReducer', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers()
|
||||
|
@ -187,12 +175,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -377,17 +362,6 @@ describe('navigateReducer', () => {
|
|||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const action: NavigateAction = {
|
||||
type: ACTION_NAVIGATE,
|
||||
url: new URL('/linking/about', 'https://localhost'),
|
||||
|
@ -395,14 +369,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => navigateReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state2, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -479,7 +448,31 @@ describe('navigateReducer', () => {
|
|||
],
|
||||
},
|
||||
"nextUrl": "/linking/about",
|
||||
"prefetchCache": Map {},
|
||||
"prefetchCache": Map {
|
||||
"/linking/about" => {
|
||||
"data": Promise {},
|
||||
"kind": "temporary",
|
||||
"lastUsedTime": 1690329600000,
|
||||
"prefetchTime": 1690329600000,
|
||||
"treeAtTimeOfPrefetch": [
|
||||
"",
|
||||
{
|
||||
"children": [
|
||||
"linking",
|
||||
{
|
||||
"children": [
|
||||
"__PAGE__",
|
||||
{},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
"pushRef": {
|
||||
"mpaNavigation": false,
|
||||
"pendingPush": true,
|
||||
|
@ -563,17 +556,6 @@ describe('navigateReducer', () => {
|
|||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const url = new URL('https://example.vercel.sh', 'https://localhost')
|
||||
const isExternalUrl = url.origin !== 'localhost'
|
||||
|
||||
|
@ -584,14 +566,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => navigateReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state2, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -716,17 +693,6 @@ describe('navigateReducer', () => {
|
|||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const url = new URL('https://example.vercel.sh', 'https://localhost')
|
||||
const isExternalUrl = url.origin !== 'localhost'
|
||||
|
||||
|
@ -737,14 +703,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'replace',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => navigateReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state2, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -859,17 +820,6 @@ describe('navigateReducer', () => {
|
|||
])
|
||||
|
||||
const state = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
|
@ -887,14 +837,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: false, // should not scroll
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => navigateReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state2, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -940,7 +885,31 @@ describe('navigateReducer', () => {
|
|||
"segmentPaths": [],
|
||||
},
|
||||
"nextUrl": "/linking",
|
||||
"prefetchCache": Map {},
|
||||
"prefetchCache": Map {
|
||||
"/linking" => {
|
||||
"data": Promise {},
|
||||
"kind": "temporary",
|
||||
"lastUsedTime": 1690329600000,
|
||||
"prefetchTime": 1690329600000,
|
||||
"treeAtTimeOfPrefetch": [
|
||||
"",
|
||||
{
|
||||
"children": [
|
||||
"linking",
|
||||
{
|
||||
"children": [
|
||||
"__PAGE__",
|
||||
{},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
"pushRef": {
|
||||
"mpaNavigation": true,
|
||||
"pendingPush": true,
|
||||
|
@ -1026,23 +995,12 @@ describe('navigateReducer', () => {
|
|||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
await runPromiseThrowChain(() => prefetchReducer(state, prefetchAction))
|
||||
await prefetchReducer(state, prefetchAction)
|
||||
|
||||
await state.prefetchCache.get(url.pathname + url.search)?.data
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
await runPromiseThrowChain(() => prefetchReducer(state2, prefetchAction))
|
||||
await state2.prefetchCache.get(url.pathname + url.search)?.data
|
||||
await prefetchReducer(state, prefetchAction)
|
||||
await state.prefetchCache.get(url.pathname + url.search)?.data
|
||||
|
||||
const action: NavigateAction = {
|
||||
type: ACTION_NAVIGATE,
|
||||
|
@ -1051,14 +1009,9 @@ describe('navigateReducer', () => {
|
|||
navigateType: 'push',
|
||||
locationSearch: '',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => navigateReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state2, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
const prom = Promise.resolve([
|
||||
[
|
||||
|
@ -1162,7 +1115,7 @@ describe('navigateReducer', () => {
|
|||
"/linking/about" => {
|
||||
"data": Promise {},
|
||||
"kind": "auto",
|
||||
"lastUsedTime": null,
|
||||
"lastUsedTime": 1690329600000,
|
||||
"prefetchTime": 1690329600000,
|
||||
"treeAtTimeOfPrefetch": [
|
||||
"",
|
||||
|
@ -1310,17 +1263,6 @@ describe('navigateReducer', () => {
|
|||
location: new URL('/parallel-tab-bar', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/parallel-tab-bar', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const action: NavigateAction = {
|
||||
type: ACTION_NAVIGATE,
|
||||
url: new URL('/parallel-tab-bar/demographics', 'https://localhost'),
|
||||
|
@ -1328,14 +1270,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => navigateReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state2, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -1428,7 +1365,39 @@ describe('navigateReducer', () => {
|
|||
],
|
||||
},
|
||||
"nextUrl": "/parallel-tab-bar/demographics",
|
||||
"prefetchCache": Map {},
|
||||
"prefetchCache": Map {
|
||||
"/parallel-tab-bar/demographics" => {
|
||||
"data": Promise {},
|
||||
"kind": "temporary",
|
||||
"lastUsedTime": 1690329600000,
|
||||
"prefetchTime": 1690329600000,
|
||||
"treeAtTimeOfPrefetch": [
|
||||
"",
|
||||
{
|
||||
"children": [
|
||||
"parallel-tab-bar",
|
||||
{
|
||||
"audience": [
|
||||
"__PAGE__",
|
||||
{},
|
||||
],
|
||||
"children": [
|
||||
"__PAGE__",
|
||||
{},
|
||||
],
|
||||
"views": [
|
||||
"__PAGE__",
|
||||
{},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
"pushRef": {
|
||||
"mpaNavigation": false,
|
||||
"pendingPush": true,
|
||||
|
@ -1520,26 +1489,16 @@ describe('navigateReducer', () => {
|
|||
location: new URL('/linking#hash', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const action: NavigateAction = {
|
||||
type: ACTION_NAVIGATE,
|
||||
url: new URL('/linking#hash', 'https://localhost'),
|
||||
isExternalUrl: false,
|
||||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
const mutable = {
|
||||
canonicalUrl: '/linking#hash',
|
||||
previousTree: initialTree,
|
||||
hashFragment: '#hash',
|
||||
pendingPush: true,
|
||||
shouldScroll: true,
|
||||
mutable: {
|
||||
canonicalUrl: '/linking#hash',
|
||||
previousTree: initialTree,
|
||||
hashFragment: '#hash',
|
||||
pendingPush: true,
|
||||
shouldScroll: true,
|
||||
preserveCustomHistoryState: false,
|
||||
},
|
||||
preserveCustomHistoryState: false,
|
||||
}
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state, action)
|
||||
)
|
||||
const newState = handleMutable(state, mutable)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -1670,12 +1629,9 @@ describe('navigateReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state, action)
|
||||
)
|
||||
const newState = await navigateReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
|
|
@ -35,7 +35,6 @@ export function handleExternalUrl(
|
|||
url: string,
|
||||
pendingPush: boolean
|
||||
) {
|
||||
mutable.previousTree = state.tree
|
||||
mutable.mpaNavigation = true
|
||||
mutable.canonicalUrl = url
|
||||
mutable.pendingPush = pendingPush
|
||||
|
@ -99,20 +98,14 @@ export function navigateReducer(
|
|||
state: ReadonlyReducerState,
|
||||
action: NavigateAction
|
||||
): ReducerState {
|
||||
const { url, isExternalUrl, navigateType, mutable, shouldScroll } = action
|
||||
const { url, isExternalUrl, navigateType, shouldScroll } = action
|
||||
const mutable: Mutable = {}
|
||||
const { hash } = url
|
||||
const href = createHrefFromUrl(url)
|
||||
const pendingPush = navigateType === 'push'
|
||||
// we want to prune the prefetch cache on every navigation to avoid it growing too large
|
||||
prunePrefetchCache(state.prefetchCache)
|
||||
|
||||
const isForCurrentTree =
|
||||
JSON.stringify(mutable.previousTree) === JSON.stringify(state.tree)
|
||||
|
||||
if (isForCurrentTree) {
|
||||
return handleMutable(state, mutable)
|
||||
}
|
||||
|
||||
mutable.preserveCustomHistoryState = false
|
||||
|
||||
if (isExternalUrl) {
|
||||
|
@ -277,7 +270,6 @@ export function navigateReducer(
|
|||
}
|
||||
}
|
||||
|
||||
mutable.previousTree = state.tree
|
||||
mutable.patchedTree = currentTree
|
||||
mutable.canonicalUrl = canonicalUrlOverride
|
||||
? createHrefFromUrl(canonicalUrlOverride)
|
||||
|
|
|
@ -135,7 +135,6 @@ describe('refreshReducer', () => {
|
|||
})
|
||||
const action: RefreshAction = {
|
||||
type: ACTION_REFRESH,
|
||||
mutable: {},
|
||||
origin: new URL('/linking', 'https://localhost').origin,
|
||||
}
|
||||
|
||||
|
@ -291,7 +290,6 @@ describe('refreshReducer', () => {
|
|||
|
||||
const action: RefreshAction = {
|
||||
type: ACTION_REFRESH,
|
||||
mutable: {},
|
||||
origin: new URL('/linking', 'https://localhost').origin,
|
||||
}
|
||||
|
||||
|
@ -473,7 +471,6 @@ describe('refreshReducer', () => {
|
|||
|
||||
const action: RefreshAction = {
|
||||
type: ACTION_REFRESH,
|
||||
mutable: {},
|
||||
origin: new URL('/linking', 'https://localhost').origin,
|
||||
}
|
||||
|
||||
|
@ -704,7 +701,6 @@ describe('refreshReducer', () => {
|
|||
|
||||
const action: RefreshAction = {
|
||||
type: ACTION_REFRESH,
|
||||
mutable: {},
|
||||
origin: new URL('/linking', 'https://localhost').origin,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import { createHrefFromUrl } from '../create-href-from-url'
|
|||
import { applyRouterStatePatchToTree } from '../apply-router-state-patch-to-tree'
|
||||
import { isNavigatingToNewRootLayout } from '../is-navigating-to-new-root-layout'
|
||||
import type {
|
||||
Mutable,
|
||||
ReadonlyReducerState,
|
||||
ReducerState,
|
||||
RefreshAction,
|
||||
|
@ -20,18 +21,12 @@ export function refreshReducer(
|
|||
state: ReadonlyReducerState,
|
||||
action: RefreshAction
|
||||
): ReducerState {
|
||||
const { mutable, origin } = action
|
||||
const { origin } = action
|
||||
const mutable: Mutable = {}
|
||||
const href = state.canonicalUrl
|
||||
|
||||
let currentTree = state.tree
|
||||
|
||||
const isForCurrentTree =
|
||||
JSON.stringify(mutable.previousTree) === JSON.stringify(currentTree)
|
||||
|
||||
if (isForCurrentTree) {
|
||||
return handleMutable(state, mutable)
|
||||
}
|
||||
|
||||
mutable.preserveCustomHistoryState = false
|
||||
|
||||
const cache: CacheNode = createEmptyCacheNode()
|
||||
|
@ -117,7 +112,6 @@ export function refreshReducer(
|
|||
mutable.prefetchCache = new Map()
|
||||
}
|
||||
|
||||
mutable.previousTree = currentTree
|
||||
mutable.patchedTree = newTree
|
||||
mutable.canonicalUrl = href
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import type {
|
|||
ReadonlyReducerState,
|
||||
ReducerState,
|
||||
ServerActionAction,
|
||||
ServerActionMutable,
|
||||
} from '../router-reducer-types'
|
||||
import { addBasePath } from '../../../add-base-path'
|
||||
import { createHrefFromUrl } from '../create-href-from-url'
|
||||
|
@ -157,18 +158,12 @@ export function serverActionReducer(
|
|||
state: ReadonlyReducerState,
|
||||
action: ServerActionAction
|
||||
): ReducerState {
|
||||
const { mutable, resolve, reject } = action
|
||||
const { resolve, reject } = action
|
||||
const mutable: ServerActionMutable = {}
|
||||
const href = state.canonicalUrl
|
||||
|
||||
let currentTree = state.tree
|
||||
|
||||
const isForCurrentTree =
|
||||
JSON.stringify(mutable.previousTree) === JSON.stringify(currentTree)
|
||||
|
||||
if (isForCurrentTree) {
|
||||
return handleMutable(state, mutable)
|
||||
}
|
||||
|
||||
mutable.preserveCustomHistoryState = false
|
||||
mutable.inFlightServerAction = fetchServerAction(state, action)
|
||||
|
||||
|
@ -183,8 +178,6 @@ export function serverActionReducer(
|
|||
mutable.pendingPush = true
|
||||
}
|
||||
|
||||
mutable.previousTree = state.tree
|
||||
|
||||
if (!flightData) {
|
||||
if (!mutable.actionResultResolved) {
|
||||
resolve(actionResult)
|
||||
|
@ -268,7 +261,6 @@ export function serverActionReducer(
|
|||
mutable.prefetchCache = new Map()
|
||||
}
|
||||
|
||||
mutable.previousTree = currentTree
|
||||
mutable.patchedTree = newTree
|
||||
mutable.canonicalUrl = href
|
||||
|
||||
|
|
|
@ -79,19 +79,6 @@ const getInitialRouterStateTree = (): FlightRouterState => [
|
|||
true,
|
||||
]
|
||||
|
||||
async function runPromiseThrowChain(fn: any): Promise<any> {
|
||||
try {
|
||||
return await fn()
|
||||
} catch (err) {
|
||||
if (err instanceof Promise) {
|
||||
await err
|
||||
return await runPromiseThrowChain(fn)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
describe('serverPatchReducer', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers()
|
||||
|
@ -171,12 +158,9 @@ describe('serverPatchReducer', () => {
|
|||
true,
|
||||
],
|
||||
overrideCanonicalUrl: undefined,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
serverPatchReducer(state, action)
|
||||
)
|
||||
const newState = await serverPatchReducer(state, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
@ -276,172 +260,6 @@ describe('serverPatchReducer', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
it('should apply server patch (concurrent)', async () => {
|
||||
const initialTree = getInitialRouterStateTree()
|
||||
const initialCanonicalUrl = '/linking'
|
||||
const children = (
|
||||
<html>
|
||||
<head></head>
|
||||
<body>Root layout</body>
|
||||
</html>
|
||||
)
|
||||
const initialParallelRoutes: CacheNode['parallelRoutes'] = new Map([
|
||||
[
|
||||
'children',
|
||||
new Map([
|
||||
[
|
||||
'linking',
|
||||
{
|
||||
status: CacheStates.READY,
|
||||
parallelRoutes: new Map([
|
||||
[
|
||||
'children',
|
||||
new Map([
|
||||
[
|
||||
'',
|
||||
{
|
||||
status: CacheStates.READY,
|
||||
data: null,
|
||||
subTreeData: <>Linking page</>,
|
||||
parallelRoutes: new Map(),
|
||||
},
|
||||
],
|
||||
]),
|
||||
],
|
||||
]),
|
||||
data: null,
|
||||
subTreeData: <>Linking layout level</>,
|
||||
},
|
||||
],
|
||||
]),
|
||||
],
|
||||
])
|
||||
|
||||
const state = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking/about', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const state2 = createInitialRouterState({
|
||||
buildId,
|
||||
initialTree,
|
||||
initialHead: null,
|
||||
initialCanonicalUrl,
|
||||
initialSeedData: ['', null, children],
|
||||
initialParallelRoutes,
|
||||
isServer: false,
|
||||
location: new URL('/linking/about', 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const action: ServerPatchAction = {
|
||||
type: ACTION_SERVER_PATCH,
|
||||
flightData: flightDataForPatch,
|
||||
previousTree: [
|
||||
'',
|
||||
{
|
||||
children: [
|
||||
'linking',
|
||||
{
|
||||
children: ['somewhere-else', { children: ['', {}] }],
|
||||
},
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
],
|
||||
overrideCanonicalUrl: undefined,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
await runPromiseThrowChain(() => serverPatchReducer(state, action))
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
serverPatchReducer(state2, action)
|
||||
)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
"buildId": "development",
|
||||
"cache": {
|
||||
"data": null,
|
||||
"parallelRoutes": Map {
|
||||
"children" => Map {
|
||||
"linking" => {
|
||||
"data": null,
|
||||
"parallelRoutes": Map {
|
||||
"children" => Map {
|
||||
"" => {
|
||||
"data": null,
|
||||
"parallelRoutes": Map {},
|
||||
"status": "READY",
|
||||
"subTreeData": <React.Fragment>
|
||||
Linking page
|
||||
</React.Fragment>,
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": "READY",
|
||||
"subTreeData": <React.Fragment>
|
||||
Linking layout level
|
||||
</React.Fragment>,
|
||||
},
|
||||
},
|
||||
},
|
||||
"status": "READY",
|
||||
"subTreeData": <html>
|
||||
<head />
|
||||
<body>
|
||||
Root layout
|
||||
</body>
|
||||
</html>,
|
||||
},
|
||||
"canonicalUrl": "/linking/about",
|
||||
"focusAndScrollRef": {
|
||||
"apply": false,
|
||||
"hashFragment": null,
|
||||
"onlyHashChange": false,
|
||||
"segmentPaths": [],
|
||||
},
|
||||
"nextUrl": "/linking/about",
|
||||
"prefetchCache": Map {},
|
||||
"pushRef": {
|
||||
"mpaNavigation": false,
|
||||
"pendingPush": false,
|
||||
"preserveCustomHistoryState": true,
|
||||
},
|
||||
"tree": [
|
||||
"",
|
||||
{
|
||||
"children": [
|
||||
"linking",
|
||||
{
|
||||
"children": [
|
||||
"about",
|
||||
{
|
||||
"children": [
|
||||
"",
|
||||
{},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
],
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
it('should apply server patch without affecting focusAndScrollRef', async () => {
|
||||
const initialTree = getInitialRouterStateTree()
|
||||
const initialCanonicalUrl = '/linking'
|
||||
|
@ -490,7 +308,6 @@ describe('serverPatchReducer', () => {
|
|||
locationSearch: '',
|
||||
navigateType: 'push',
|
||||
shouldScroll: true,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
const state = createInitialRouterState({
|
||||
|
@ -504,9 +321,7 @@ describe('serverPatchReducer', () => {
|
|||
location: new URL(initialCanonicalUrl, 'https://localhost') as any,
|
||||
})
|
||||
|
||||
const stateAfterNavigate = await runPromiseThrowChain(() =>
|
||||
navigateReducer(state, navigateAction)
|
||||
)
|
||||
const stateAfterNavigate = await navigateReducer(state, navigateAction)
|
||||
|
||||
const action: ServerPatchAction = {
|
||||
type: ACTION_SERVER_PATCH,
|
||||
|
@ -526,12 +341,9 @@ describe('serverPatchReducer', () => {
|
|||
true,
|
||||
],
|
||||
overrideCanonicalUrl: undefined,
|
||||
mutable: {},
|
||||
}
|
||||
|
||||
const newState = await runPromiseThrowChain(() =>
|
||||
serverPatchReducer(stateAfterNavigate, action)
|
||||
)
|
||||
const newState = await serverPatchReducer(stateAfterNavigate, action)
|
||||
|
||||
expect(newState).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@ import type {
|
|||
ServerPatchAction,
|
||||
ReducerState,
|
||||
ReadonlyReducerState,
|
||||
Mutable,
|
||||
} from '../router-reducer-types'
|
||||
import { handleExternalUrl } from './navigate-reducer'
|
||||
import { applyFlightData } from '../apply-flight-data'
|
||||
|
@ -16,23 +17,9 @@ export function serverPatchReducer(
|
|||
state: ReadonlyReducerState,
|
||||
action: ServerPatchAction
|
||||
): ReducerState {
|
||||
const { flightData, previousTree, overrideCanonicalUrl, mutable } = action
|
||||
const { flightData, overrideCanonicalUrl } = action
|
||||
|
||||
const isForCurrentTree =
|
||||
JSON.stringify(previousTree) === JSON.stringify(state.tree)
|
||||
|
||||
// When a fetch is slow to resolve it could be that you navigated away while the request was happening or before the reducer runs.
|
||||
// In that case opt-out of applying the patch given that the data could be stale.
|
||||
if (!isForCurrentTree) {
|
||||
// TODO-APP: Handle tree mismatch
|
||||
console.log('TREE MISMATCH')
|
||||
// Keep everything as-is.
|
||||
return state
|
||||
}
|
||||
|
||||
if (mutable.previousTree) {
|
||||
return handleMutable(state, mutable)
|
||||
}
|
||||
const mutable: Mutable = {}
|
||||
|
||||
mutable.preserveCustomHistoryState = false
|
||||
|
||||
|
@ -85,7 +72,6 @@ export function serverPatchReducer(
|
|||
const cache: CacheNode = createEmptyCacheNode()
|
||||
applyFlightData(currentCache, cache, flightDataPath)
|
||||
|
||||
mutable.previousTree = currentTree
|
||||
mutable.patchedTree = newTree
|
||||
mutable.cache = cache
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ export type RouterNavigate = (
|
|||
|
||||
export interface Mutable {
|
||||
mpaNavigation?: boolean
|
||||
previousTree?: FlightRouterState
|
||||
patchedTree?: FlightRouterState
|
||||
canonicalUrl?: string
|
||||
scrollableSegments?: FlightSegmentPath[]
|
||||
|
@ -52,13 +51,11 @@ export interface ServerActionMutable extends Mutable {
|
|||
*/
|
||||
export interface RefreshAction {
|
||||
type: typeof ACTION_REFRESH
|
||||
mutable: Mutable
|
||||
origin: Location['origin']
|
||||
}
|
||||
|
||||
export interface FastRefreshAction {
|
||||
type: typeof ACTION_FAST_REFRESH
|
||||
mutable: Mutable
|
||||
origin: Location['origin']
|
||||
}
|
||||
|
||||
|
@ -75,7 +72,6 @@ export interface ServerActionAction {
|
|||
actionArgs: any[]
|
||||
resolve: (value: any) => void
|
||||
reject: (reason?: any) => void
|
||||
mutable: ServerActionMutable
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,7 +111,6 @@ export interface NavigateAction {
|
|||
locationSearch: Location['search']
|
||||
navigateType: 'push' | 'replace'
|
||||
shouldScroll: boolean
|
||||
mutable: Mutable
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +136,6 @@ export interface ServerPatchAction {
|
|||
flightData: FlightData
|
||||
previousTree: FlightRouterState
|
||||
overrideCanonicalUrl: URL | undefined
|
||||
mutable: Mutable
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -80,7 +80,6 @@ async function runAction({
|
|||
actionQueue.dispatch(
|
||||
{
|
||||
type: ACTION_REFRESH,
|
||||
mutable: {},
|
||||
origin: window.location.origin,
|
||||
},
|
||||
setState
|
||||
|
|
Loading…
Reference in a new issue