Skip to content

Commit

Permalink
fix(react-router): navigate if redirect is thrown in component
Browse files Browse the repository at this point in the history
  • Loading branch information
schiller-manuel committed Jan 3, 2025
1 parent 5c8c38a commit 5d0ea08
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
11 changes: 11 additions & 0 deletions packages/react-router/src/CatchBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as React from 'react'
import { useRouter } from './useRouter'
import { isRedirect } from './redirects'
import type { ErrorRouteComponent } from './route'
import type { ErrorInfo } from 'react'

Expand All @@ -9,13 +11,22 @@ export function CatchBoundary(props: {
onCatch?: (error: Error, errorInfo: ErrorInfo) => void
}) {
const errorComponent = props.errorComponent ?? ErrorComponent
const router = useRouter()

return (
<CatchBoundaryImpl
getResetKey={props.getResetKey}
onCatch={props.onCatch}
children={({ error, reset }) => {
if (error) {
if (isRedirect(error)) {
const redirect = router.resolveRedirect({
...error,
_fromLocation: router.state.location,
})
router.navigate(redirect)
return null
}
return React.createElement(errorComponent, {
error,
reset,
Expand Down
6 changes: 5 additions & 1 deletion packages/react-router/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2696,7 +2696,11 @@ export class Router<
}))
} catch (err) {
if (isResolvedRedirect(err)) {
await this.navigate(err)
await this.navigate({
...err,
replace: true,
ignoreBlocker: true,
})
}
}
})()
Expand Down
78 changes: 78 additions & 0 deletions packages/react-router/tests/redirect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,84 @@ describe('redirect', () => {
expect(await screen.findByText('Final')).toBeInTheDocument()
expect(window.location.pathname).toBe('/final')
})

test('when `redirect` is thrown in route component', async () => {
const nestedLoaderMock = vi.fn()
const nestedFooLoaderMock = vi.fn()

const rootRoute = createRootRoute({})
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => {
return (
<div>
<h1>Index page</h1>
<Link to="/about">link to about</Link>
</div>
)
},
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/about',
component: () => {
throw redirect({
to: '/nested/foo',
hash: 'some-hash',
search: { someSearch: 'hello123' },
})
},
})
const nestedRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/nested',
loader: async () => {
await sleep(WAIT_TIME)
nestedLoaderMock('nested')
},
})
const fooRoute = createRoute({
validateSearch: (search) => {
return {
someSearch: search.someSearch as string,
}
},
getParentRoute: () => nestedRoute,
path: '/foo',
loader: async () => {
await sleep(WAIT_TIME)
nestedFooLoaderMock('foo')
},
component: () => <div>Nested Foo page</div>,
})
const routeTree = rootRoute.addChildren([
nestedRoute.addChildren([fooRoute]),
aboutRoute,
indexRoute,
])
const router = createRouter({ routeTree })

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

const linkToAbout = await screen.findByText('link to about')

expect(linkToAbout).toBeInTheDocument()

fireEvent.click(linkToAbout)

const fooElement = await screen.findByText('Nested Foo page')

expect(fooElement).toBeInTheDocument()

expect(router.state.location.href).toBe(
'/nested/foo?someSearch=hello123#some-hash',
)
expect(window.location.pathname).toBe('/nested/foo')

expect(nestedLoaderMock).toHaveBeenCalled()
expect(nestedFooLoaderMock).toHaveBeenCalled()
})
})

describe('SSR', () => {
Expand Down

0 comments on commit 5d0ea08

Please sign in to comment.