Skip to content

Commit 9a22133

Browse files
committed
fix(router-core): on navigate (with preload), route component isn't rendered with undefined context if beforeLoad is pending
1 parent 3f05c0b commit 9a22133

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

packages/react-router/tests/routeContext.test.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3125,4 +3125,87 @@ describe('useRouteContext in the component', () => {
31253125

31263126
expect(content).toBeInTheDocument()
31273127
})
3128-
})
3128+
3129+
test("reproducer #4998 - on navigate (with preload), route component isn't rendered with undefined context if beforeLoad is pending", async () => {
3130+
const beforeLoad = vi.fn()
3131+
const select = vi.fn()
3132+
let resolved = 0
3133+
const rootRoute = createRootRoute()
3134+
const indexRoute = createRoute({
3135+
getParentRoute: () => rootRoute,
3136+
path: '/',
3137+
component: () => <Link to="/foo">To foo</Link>,
3138+
})
3139+
const fooRoute = createRoute({
3140+
getParentRoute: () => rootRoute,
3141+
path: '/foo',
3142+
beforeLoad: async (...args) => {
3143+
beforeLoad(...args)
3144+
await sleep(WAIT_TIME)
3145+
resolved += 1
3146+
return { foo: resolved }
3147+
},
3148+
component: () => {
3149+
fooRoute.useRouteContext({ select })
3150+
return <h1>Foo index page</h1>
3151+
},
3152+
pendingComponent: () => 'loading',
3153+
})
3154+
const routeTree = rootRoute.addChildren([
3155+
indexRoute,
3156+
fooRoute
3157+
])
3158+
const router = createRouter({
3159+
routeTree,
3160+
history,
3161+
defaultPreload: 'intent',
3162+
defaultPendingMs: 0,
3163+
})
3164+
3165+
render(<RouterProvider router={router} />)
3166+
const linkToFoo = await screen.findByText('To foo')
3167+
3168+
fireEvent.focus(linkToFoo)
3169+
expect(beforeLoad).toHaveBeenCalledTimes(1)
3170+
expect(resolved).toBe(0)
3171+
3172+
await sleep(WAIT_TIME)
3173+
3174+
expect(beforeLoad).toHaveBeenCalledTimes(1)
3175+
expect(resolved).toBe(1)
3176+
expect(beforeLoad).toHaveBeenNthCalledWith(1, expect.objectContaining({
3177+
cause: 'preload',
3178+
preload: true,
3179+
}))
3180+
3181+
expect(select).not.toHaveBeenCalled()
3182+
3183+
fireEvent.click(linkToFoo)
3184+
expect(beforeLoad).toHaveBeenCalledTimes(2)
3185+
expect(resolved).toBe(1)
3186+
3187+
await screen.findByText('Foo index page')
3188+
3189+
expect(beforeLoad).toHaveBeenCalledTimes(2)
3190+
expect(beforeLoad).toHaveBeenNthCalledWith(2, expect.objectContaining({
3191+
cause: 'enter',
3192+
preload: false,
3193+
}))
3194+
expect(select).toHaveBeenNthCalledWith(1, expect.objectContaining({
3195+
foo: 1,
3196+
}))
3197+
expect(select).not.toHaveBeenCalledWith({})
3198+
3199+
await sleep(WAIT_TIME)
3200+
expect(beforeLoad).toHaveBeenCalledTimes(2)
3201+
expect(resolved).toBe(2)
3202+
3203+
// ensure the context has been updated once the beforeLoad has resolved
3204+
expect(select).toHaveBeenLastCalledWith(expect.objectContaining({
3205+
foo: 2,
3206+
}))
3207+
3208+
// the route component will be rendered multiple times, ensure it always has the context
3209+
expect(select).not.toHaveBeenCalledWith({})
3210+
})
3211+
})

packages/router-core/src/load-matches.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ const executeBeforeLoad = (
369369
const parentMatchContext =
370370
parentMatch?.context ?? inner.router.options.context ?? undefined
371371

372-
const context = { ...parentMatchContext, ...match.__routeContext }
372+
const context = { ...match.__beforeLoadContext, ...parentMatchContext, ...match.__routeContext }
373373

374374
let isPending = false
375375
const pending = () => {

0 commit comments

Comments
 (0)