diff --git a/packages/react-router/tests/routeContext.test.tsx b/packages/react-router/tests/routeContext.test.tsx
index 7a38a272d9..c439b7f690 100644
--- a/packages/react-router/tests/routeContext.test.tsx
+++ b/packages/react-router/tests/routeContext.test.tsx
@@ -3125,4 +3125,95 @@ describe('useRouteContext in the component', () => {
expect(content).toBeInTheDocument()
})
+
+ test("reproducer #4998 - on navigate (with preload), route component isn't rendered with undefined context if beforeLoad is pending", async () => {
+ const beforeLoad = vi.fn()
+ const select = vi.fn()
+ let resolved = 0
+ const rootRoute = createRootRoute()
+ const indexRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/',
+ component: () => To foo,
+ })
+ const fooRoute = createRoute({
+ getParentRoute: () => rootRoute,
+ path: '/foo',
+ beforeLoad: async (...args) => {
+ beforeLoad(...args)
+ await sleep(WAIT_TIME)
+ resolved += 1
+ return { foo: resolved }
+ },
+ component: () => {
+ fooRoute.useRouteContext({ select })
+ return
Foo index page
+ },
+ pendingComponent: () => 'loading',
+ })
+ const routeTree = rootRoute.addChildren([indexRoute, fooRoute])
+ const router = createRouter({
+ routeTree,
+ history,
+ defaultPreload: 'intent',
+ defaultPendingMs: 0,
+ })
+
+ render()
+ const linkToFoo = await screen.findByText('To foo')
+
+ fireEvent.focus(linkToFoo)
+ expect(beforeLoad).toHaveBeenCalledTimes(1)
+ expect(resolved).toBe(0)
+
+ await sleep(WAIT_TIME)
+
+ expect(beforeLoad).toHaveBeenCalledTimes(1)
+ expect(resolved).toBe(1)
+ expect(beforeLoad).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ cause: 'preload',
+ preload: true,
+ }),
+ )
+
+ expect(select).not.toHaveBeenCalled()
+
+ fireEvent.click(linkToFoo)
+ expect(beforeLoad).toHaveBeenCalledTimes(2)
+ expect(resolved).toBe(1)
+
+ await screen.findByText('Foo index page')
+
+ expect(beforeLoad).toHaveBeenCalledTimes(2)
+ expect(beforeLoad).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({
+ cause: 'enter',
+ preload: false,
+ }),
+ )
+ expect(select).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ foo: 1,
+ }),
+ )
+ expect(select).not.toHaveBeenCalledWith({})
+
+ await sleep(WAIT_TIME)
+ expect(beforeLoad).toHaveBeenCalledTimes(2)
+ expect(resolved).toBe(2)
+
+ // ensure the context has been updated once the beforeLoad has resolved
+ expect(select).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ foo: 2,
+ }),
+ )
+
+ // the route component will be rendered multiple times, ensure it always has the context
+ expect(select).not.toHaveBeenCalledWith({})
+ })
})
diff --git a/packages/router-core/src/load-matches.ts b/packages/router-core/src/load-matches.ts
index c1edd5648f..a38af5379a 100644
--- a/packages/router-core/src/load-matches.ts
+++ b/packages/router-core/src/load-matches.ts
@@ -369,7 +369,11 @@ const executeBeforeLoad = (
const parentMatchContext =
parentMatch?.context ?? inner.router.options.context ?? undefined
- const context = { ...parentMatchContext, ...match.__routeContext }
+ const context = {
+ ...match.__beforeLoadContext,
+ ...parentMatchContext,
+ ...match.__routeContext,
+ }
let isPending = false
const pending = () => {