Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import * as React from 'react'
import { act, cleanup, render, screen } from '@testing-library/react'
import { afterEach, expect, test, vi } from 'vitest'
import {
RouterProvider,
createMemoryHistory,
createRootRoute,
createRouter,
} from '../src'

afterEach(() => {
cleanup()
vi.useRealTimers()
})

// https://github.com/TanStack/router/issues/8115
test('#8115: a reloading match keeps its previous beforeLoad context until the new result lands', async () => {
vi.useFakeTimers()

const observed: Array<unknown> = []
let runs = 0
const rootRoute = createRootRoute({
beforeLoad: async ({ matches }) => {
runs++
if (runs > 1) {
observed.push((matches[0] as any).context?.locale)
}
await new Promise((resolve) => setTimeout(resolve, 100))
return { locale: 'en' }
},
component: () => <div data-testid="content">ok</div>,
})
const router = createRouter({
routeTree: rootRoute,
history: createMemoryHistory({ initialEntries: ['/'] }),
})

render(<RouterProvider router={router} />)
await act(async () => {
await vi.advanceTimersByTimeAsync(100)
})
expect(screen.getByTestId('content')).toBeInTheDocument()

await act(async () => {
void router.invalidate()
await vi.advanceTimersByTimeAsync(100)
})

expect(runs).toBe(2)
expect(observed).toEqual(['en'])
})
12 changes: 11 additions & 1 deletion packages/router-core/src/load-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,13 @@ async function contextualize(
...parentContext,
...routeContext,
}
match.context = context
// Seeded from the committed match so a reloading match never exposes a
// context stripped of its previous beforeLoad result (#8115).
const base = options[2 /* base */][index]
match.context =
base?.id === match.id && base.context
? { ...base.context, ...context }
: context
} catch (cause) {
releaseFlight(router, match)
return [index, normalizeLaneError(router, lane, route, cause, options)]
Expand All @@ -402,6 +408,7 @@ async function contextualize(
}
const validationError = match.paramsError ?? match.searchError
if (validationError !== undefined) {
match.context = context
releaseFlight(router, match)
return [
index,
Expand All @@ -410,6 +417,7 @@ async function contextualize(
}
const beforeLoad = route.options.beforeLoad
if (!beforeLoad) {
match.context = context
continue
}

Expand Down Expand Up @@ -449,6 +457,7 @@ async function contextualize(
options,
)
if (outcome[0 /* kind */] !== SUCCESS) {
match.context = context
releaseFlight(router, match)
return [index, outcome]
}
Expand All @@ -457,6 +466,7 @@ async function contextualize(
...result,
}
} catch (cause) {
match.context = context
releaseFlight(router, match)
return [index, normalizeLaneError(router, lane, route, cause, options)]
} finally {
Expand Down