diff --git a/packages/react-router/src/CatchBoundary.tsx b/packages/react-router/src/CatchBoundary.tsx index f55a250ab1..87203a2279 100644 --- a/packages/react-router/src/CatchBoundary.tsx +++ b/packages/react-router/src/CatchBoundary.tsx @@ -11,32 +11,36 @@ export class CatchBoundary extends React.Component<{ errorComponent?: ErrorRouteComponent onCatch?: (error: Error, errorInfo: ErrorInfo) => void }> { - state = { error: null } as { error: Error | null; resetKey?: unknown } + state = { error: null, hasError: false } as { + error: Error | null + hasError: boolean + resetKey?: unknown + } static getDerivedStateFromProps( props: { getResetKey: () => unknown }, - state: { resetKey?: unknown; error: Error | null }, + state: { resetKey?: unknown; error: Error | null; hasError: boolean }, ) { const resetKey = props.getResetKey() - if (state.error && state.resetKey !== resetKey) { - return { resetKey, error: null } + if (state.hasError && state.resetKey !== resetKey) { + return { resetKey, error: null, hasError: false } } return { resetKey } } static getDerivedStateFromError(error: Error) { - return { error } + return { error, hasError: true } } reset = () => { - this.setState({ error: null }) + this.setState({ error: null, hasError: false }) } componentDidCatch(error: Error, errorInfo: ErrorInfo) { this.props.onCatch?.(error, errorInfo) } render() { - const error = this.state.error - if (error) { + if (this.state.hasError) { + const error = this.state.error const element = React.createElement( this.props.errorComponent ?? ErrorComponent, { @@ -88,7 +92,7 @@ export function ErrorComponent({ error }: { error: any }) { overflow: 'auto', }} > - {error.message ? {error.message} : null} + {error?.message ? {error.message} : null} ) : null} diff --git a/packages/react-router/tests/errorComponent.test.tsx b/packages/react-router/tests/errorComponent.test.tsx index b5ee4b6709..d6e577acf9 100644 --- a/packages/react-router/tests/errorComponent.test.tsx +++ b/packages/react-router/tests/errorComponent.test.tsx @@ -868,3 +868,36 @@ describe('notFoundComponent is rendered when an error is thrown in params.parse' expect(notFoundComponent).toBeInTheDocument() }) }) + +test.each([ + { desc: 'undefined', value: undefined, expected: 'undefined' }, + { desc: 'null', value: null, expected: 'null' }, + { desc: 'empty string', value: '', expected: '""' }, +])( + 'errorComponent is rendered when component throws falsy value: $desc', + async ({ value, expected }) => { + const rootRoute = createRootRoute() + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: function Home() { + throw value + }, + errorComponent: ({ error }) => ( +
Caught falsy error: {JSON.stringify(error) ?? 'undefined'}
+ ), + }) + + const router = createRouter({ + routeTree: rootRoute.addChildren([indexRoute]), + history: createMemoryHistory({ initialEntries: ['/'] }), + }) + + render() + + expect( + await screen.findByText(`Caught falsy error: ${expected}`), + ).toBeInTheDocument() + }, +) +