diff --git a/packages/react-router/tests/link.test.tsx b/packages/react-router/tests/link.test.tsx index 4a8b15a959..fab45d8f0f 100644 --- a/packages/react-router/tests/link.test.tsx +++ b/packages/react-router/tests/link.test.tsx @@ -1184,6 +1184,55 @@ describe('Link', () => { expect(onError).toHaveBeenCalledOnce() }) + test('when navigating to /posts with a beforeLoad that throws an primitive type error', async () => { + const onError = vi.fn() + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () => { + return ( + <> +

Index

+ Posts + + ) + }, + }) + + const PostsComponent = () => { + return

Posts

+ } + + const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts', + beforeLoad: () => { + throw 'This is a string error' + }, + onError, + errorComponent: () => Oops! Something went wrong!, + component: PostsComponent, + }) + + const router = createRouter({ + context: { userId: 'userId' }, + routeTree: rootRoute.addChildren([indexRoute, postsRoute]), + history, + }) + + render() + + const postsLink = await screen.findByRole('link', { name: 'Posts' }) + + await act(() => fireEvent.click(postsLink)) + + const errorText = await screen.findByText('Oops! Something went wrong!') + expect(errorText).toBeInTheDocument() + + expect(onError).toHaveBeenCalledOnce() + }) + test('when navigating to /posts with a beforeLoad that throws an error bubbles to the root', async () => { const rootRoute = createRootRoute({ errorComponent: () => Oops! Something went wrong!, diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index 5d01b977f0..63efff3905 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -15,7 +15,7 @@ import type { } from './Matches' import type { RootRouteId } from './root' import type { ParseRoute, RouteById, RoutePaths } from './routeInfo' -import type { AnyRouter, RegisteredRouter } from './router' +import type { AnyRouter, RegisteredRouter, RouterCode } from './router' import type { BuildLocationFn, NavigateFn } from './RouterProvider' import type { Assign, @@ -1072,7 +1072,7 @@ export interface UpdatableRouteOptions< SearchFilter> > onCatch?: (error: Error) => void - onError?: (err: any) => void + onError?: (err: any, routerCode?: RouterCode) => void // These functions are called as route matches are loaded, stick around and leave the active // matches onEnter?: ( diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index 30cb77f74a..8c28e967e2 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -679,6 +679,8 @@ export type AnyRouterWithContext = RouterCore< export type AnyRouter = RouterCore +export type RouterCode = 'BEFORE_LOAD' | 'PARSE_PARAMS' | 'VALIDATE_SEARCH' + export interface ViewTransitionOptions { types: | Array @@ -2106,7 +2108,11 @@ export class RouterCore< triggerOnReady() } - const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => { + const handleRedirectAndNotFound = ( + match: AnyRouteMatch, + err: any, + routerCode?: RouterCode, + ) => { if (isRedirect(err) || isNotFound(err)) { if (isRedirect(err)) { if (err.redirectHandled) { @@ -2145,7 +2151,7 @@ export class RouterCore< err = this.resolveRedirect(err) throw err } else if (isNotFound(err)) { - this._handleNotFound(matches, err, { + this._handleNotFound(matches, err, routerCode, { updateMatch, }) throw err @@ -2175,7 +2181,7 @@ export class RouterCore< const handleSerialError = ( index: number, err: any, - routerCode: string, + routerCode: RouterCode, ) => { const { id: matchId, routeId } = matches[index]! const route = this.looseRoutesById[routeId]! @@ -2187,15 +2193,22 @@ export class RouterCore< throw err } - err.routerCode = routerCode firstBadMatchIndex = firstBadMatchIndex ?? index - handleRedirectAndNotFound(this.getMatch(matchId)!, err) + handleRedirectAndNotFound( + this.getMatch(matchId)!, + err, + routerCode, + ) try { - route.options.onError?.(err) + route.options.onError?.(err, routerCode) } catch (errorHandlerErr) { err = errorHandlerErr - handleRedirectAndNotFound(this.getMatch(matchId)!, err) + handleRedirectAndNotFound( + this.getMatch(matchId)!, + err, + routerCode, + ) } updateMatch(matchId, (prev) => { @@ -3019,6 +3032,7 @@ export class RouterCore< _handleNotFound = ( matches: Array, err: NotFoundError, + routerCode?: string, { updateMatch = this.updateMatch, }: { @@ -3069,10 +3083,9 @@ export class RouterCore< error: err, isFetching: false, })) - - if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) { + if (routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) { err.routeId = routeCursor.parentRoute.id - this._handleNotFound(matches, err, { + this._handleNotFound(matches, err, routerCode, { updateMatch, }) }