Skip to content
Merged
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
8 changes: 0 additions & 8 deletions examples/react/quickstart-file-based/src/routes/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@ import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({
component: AboutComponent,
beforeLoad: async () => {
await new Promise((resolve) => setTimeout(resolve, 2000))
return { someData: 'hello' }
},
loader: async ({ context }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
console.debug(context.someData)
},
})

function AboutComponent() {
Expand Down
4 changes: 4 additions & 0 deletions packages/react-router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1542,7 +1542,9 @@ export class Router<
matches: pendingMatches,
location: next,
checkLatest: () => this.checkLatest(promise),
// eslint-disable-next-line ts/require-await
onReady: async () => {
// eslint-disable-next-line ts/require-await
this.startViewTransition(async () => {
// this.viewTransitionPromise = createControlledPromise<true>()

Expand Down Expand Up @@ -1912,6 +1914,7 @@ export class Router<
handleSerialError(searchError, 'VALIDATE_SEARCH')
}

// Actually run the beforeLoad function and get the context
try {
const beforeLoadContext = await runBeforeLoad()
checkLatest()
Expand Down Expand Up @@ -2041,6 +2044,7 @@ export class Router<
}
}

// Actually run the loader and handle the result
try {
let loaderData = await runLoader()
if (this.serializeLoaderData) {
Expand Down
50 changes: 47 additions & 3 deletions packages/react-router/tests/createLazyRoute.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import React, { act } from 'react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import '@testing-library/jest-dom/vitest'
import { cleanup, configure, render, screen } from '@testing-library/react'
import {
RouterHistory,
Link,
RouterProvider,
createBrowserHistory,
createMemoryHistory,
createRootRoute,
createRoute,
createRouter,
} from '../src'
import { cleanup } from '@testing-library/react'
import type { RouterHistory } from '../src'

// TODO: Move this setup logic including the '@testing-library/jest-dom/vitest' into its own setup file
// @ts-expect-error
global.IS_REACT_ACT_ENVIRONMENT = true

afterEach(() => {
vi.resetAllMocks()
Expand All @@ -18,7 +27,16 @@ function createTestRouter(initialHistory?: RouterHistory) {
initialHistory ?? createMemoryHistory({ initialEntries: ['/'] })

const rootRoute = createRootRoute({})
const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/' })
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => (
<div>
<p>Index Route</p>
<Link to="/heavy">Link to heavy</Link>
</div>
),
})

const heavyRoute = createRoute({
getParentRoute: () => rootRoute,
Expand All @@ -36,6 +54,8 @@ function createTestRouter(initialHistory?: RouterHistory) {
}

describe('preload: matched routes', { timeout: 20000 }, () => {
configure({ reactStrictMode: true })

it('should wait for lazy options to be streamed in before ', async () => {
const { router } = createTestRouter(
createMemoryHistory({ initialEntries: ['/'] }),
Expand All @@ -55,4 +75,28 @@ describe('preload: matched routes', { timeout: 20000 }, () => {

expect(lazyRoute.options.component).toBeDefined()
})

it('should render the heavy/lazy component', async () => {
const { router } = createTestRouter(createBrowserHistory())

await act(() => render(<RouterProvider router={router} />))

const linkToHeavy = await screen.findByText('Link to heavy')
expect(linkToHeavy).toBeInTheDocument()

expect(router.state.location.pathname).toBe('/')
expect(window.location.pathname).toBe('/')

// click the link to navigate to the heavy route
act(() => linkToHeavy.click())

const heavyElement = await screen.findByText('I am sooo heavy')
expect(heavyElement).toBeInTheDocument()

expect(router.state.location.pathname).toBe('/heavy')
expect(window.location.pathname).toBe('/heavy')

const lazyRoute = router.routesByPath['/heavy']
expect(lazyRoute.options.component).toBeDefined()
})
})
68 changes: 66 additions & 2 deletions packages/react-router/tests/routeContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,8 @@ describe('beforeLoad in the route definition', () => {
expect(mock).toHaveBeenCalledTimes(1)
})

test("on navigate (with preload), loader isn't invoked with undefined context if beforeLoad is pending when navigation happens", async () => {
// TODO: Move this test to the loader section
test("on navigate (with preload using router methods), loader isn't invoked with undefined context if beforeLoad is pending when navigation happens", async () => {
const mock = vi.fn()

const rootRoute = createRootRoute()
Expand All @@ -403,7 +404,10 @@ describe('beforeLoad in the route definition', () => {
})

const routeTree = rootRoute.addChildren([aboutRoute, indexRoute])
const router = createRouter({ routeTree, context: { foo: 'bar' } })
const router = createRouter({
routeTree,
context: { foo: 'bar' },
})

await router.load()

Expand All @@ -417,6 +421,66 @@ describe('beforeLoad in the route definition', () => {
expect(mock).toHaveBeenCalledTimes(2)
})

// TODO: Move this test to the loader section
test("on navigate (with preload), loader isn't invoked with undefined context if beforeLoad is pending when navigation happens", async () => {
const mock = vi.fn()

const rootRoute = createRootRoute()
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => {
return (
<div>
<h1>Index page</h1>
<Link to="/about" preload="intent">
link to about
</Link>
</div>
)
},
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
beforeLoad: async () => {
await sleep(WAIT_TIME) // Use a longer delay here
return { mock }
},
loader: async ({ context }) => {
await sleep(WAIT_TIME)
context.mock()
},
component: () => <div>About page</div>,
})

const routeTree = rootRoute.addChildren([aboutRoute, indexRoute])
const router = createRouter({
routeTree,
defaultPreload: 'intent',
context: { foo: 'bar' },
})

await act(() => render(<RouterProvider router={router} />))

const linkToAbout = await screen.findByRole('link', {
name: 'link to about',
})
expect(linkToAbout).toBeInTheDocument()

// Don't await, simulate user clicking before preload is done
linkToAbout.focus()
linkToAbout.click()

const aboutElement = await screen.findByText('About page')
expect(aboutElement).toBeInTheDocument()

expect(window.location.pathname).toBe('/about')

// Expect double call: once from preload, once from navigate
expect(mock).toHaveBeenCalledTimes(2)
})

// Check if context returned by /nested/about, is the same as its parent route /nested on navigate
test('nested destination on navigate, route context in the /nested/about route is correctly inherited from the /nested parent', async () => {
const mock = vi.fn()
Expand Down