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:
Andrew Clark 2023-12-04 11:31:41 -05:00 committed by GitHub
parent b2e183ec1d
commit 65a74b1bef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 120 additions and 410 deletions

View file

@ -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,
})
})

View file

@ -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

View file

@ -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(`
{

View file

@ -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)

View file

@ -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,
}

View file

@ -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

View file

@ -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

View file

@ -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(`
{

View file

@ -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

View file

@ -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
}
/**

View file

@ -80,7 +80,6 @@ async function runAction({
actionQueue.dispatch(
{
type: ACTION_REFRESH,
mutable: {},
origin: window.location.origin,
},
setState