Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
91 changes: 91 additions & 0 deletions packages/react-router/tests/router.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,97 @@ describe('invalidate', () => {
expect(match.invalid).toBe(false)
})
})

it('re-runs loaders that throw notFound() when invalidated via HMR filter', async () => {
const history = createMemoryHistory({
initialEntries: ['/hmr-not-found'],
})
const loader = vi.fn(() => {
throw notFound()
})

const rootRoute = createRootRoute({
component: () => <Outlet />,
})

const hmrRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/hmr-not-found',
loader,
component: () => <div data-testid="hmr-route">Route</div>,
notFoundComponent: () => (
<div data-testid="hmr-route-not-found">Route Not Found</div>
),
})

const router = createRouter({
routeTree: rootRoute.addChildren([hmrRoute]),
history,
})

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

await act(() => router.load())

expect(await screen.findByTestId('hmr-route-not-found')).toBeInTheDocument()
const initialCalls = loader.mock.calls.length
expect(initialCalls).toBeGreaterThan(0)

await act(() =>
router.invalidate({
filter: (match) => match.routeId === hmrRoute.id,
}),
)

expect(loader).toHaveBeenCalledTimes(initialCalls + 1)
expect(await screen.findByTestId('hmr-route-not-found')).toBeInTheDocument()
expect(screen.queryByTestId('hmr-route')).not.toBeInTheDocument()
})

it('keeps rendering a route notFoundComponent when loader returns notFound() after invalidate', async () => {
const history = createMemoryHistory({
initialEntries: ['/loader-not-found'],
})
const loader = vi.fn(() => notFound())

const rootRoute = createRootRoute({
component: () => <Outlet />,
})

const loaderRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/loader-not-found',
loader,
component: () => <div data-testid="loader-route">Route</div>,
notFoundComponent: () => (
<div data-testid="loader-not-found-component">Route Not Found</div>
),
})

const router = createRouter({
routeTree: rootRoute.addChildren([loaderRoute]),
history,
})

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

await act(() => router.load())

const notFoundElement = await screen.findByTestId(
'loader-not-found-component',
)
expect(notFoundElement).toBeInTheDocument()
const initialCalls = loader.mock.calls.length
expect(initialCalls).toBeGreaterThan(0)

await act(() => router.invalidate())

expect(loader).toHaveBeenCalledTimes(initialCalls + 1)
expect(
await screen.findByTestId('loader-not-found-component'),
).toBeInTheDocument()
expect(screen.queryByTestId('loader-route')).not.toBeInTheDocument()
})
})

describe('search params in URL', () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/router-core/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2318,7 +2318,9 @@ export class RouterCore<
return {
...d,
invalid: true,
...(opts?.forcePending || d.status === 'error'
...(opts?.forcePending ||
d.status === 'error' ||
d.status === 'notFound'
? ({ status: 'pending', error: undefined } as const)
: undefined),
}
Expand Down