From f907711225969c5cfea7dd557b2a0feeadb8e2af Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Tue, 22 Jul 2025 20:14:42 +0900 Subject: [PATCH 1/9] fix(router): spread data props in notFoundComponent Signed-off-by: leesb971204 --- packages/react-router/src/renderRouteNotFound.tsx | 4 ++-- packages/solid-router/src/renderRouteNotFound.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-router/src/renderRouteNotFound.tsx b/packages/react-router/src/renderRouteNotFound.tsx index 26cf5b0928..86f31dfc22 100644 --- a/packages/react-router/src/renderRouteNotFound.tsx +++ b/packages/react-router/src/renderRouteNotFound.tsx @@ -10,7 +10,7 @@ export function renderRouteNotFound( ) { if (!route.options.notFoundComponent) { if (router.options.defaultNotFoundComponent) { - return + return } if (process.env.NODE_ENV === 'development') { @@ -23,5 +23,5 @@ export function renderRouteNotFound( return } - return + return } diff --git a/packages/solid-router/src/renderRouteNotFound.tsx b/packages/solid-router/src/renderRouteNotFound.tsx index b7421d863c..4603e6c0a3 100644 --- a/packages/solid-router/src/renderRouteNotFound.tsx +++ b/packages/solid-router/src/renderRouteNotFound.tsx @@ -9,7 +9,7 @@ export function renderRouteNotFound( ) { if (!route.options.notFoundComponent) { if (router.options.defaultNotFoundComponent) { - return + return } if (process.env.NODE_ENV === 'development') { @@ -22,5 +22,5 @@ export function renderRouteNotFound( return } - return + return } From a3bdffdae4369fe732b48d8a0ddc054441303423 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Tue, 22 Jul 2025 20:57:15 +0900 Subject: [PATCH 2/9] refactor(router-core): change proper NotFoundRouteProps type definition Signed-off-by: leesb971204 --- packages/router-core/src/route.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index b89549cdc6..4b5d81ff9b 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -1322,10 +1322,7 @@ export type ErrorComponentProps = { info?: { componentStack: string } reset: () => void } -export type NotFoundRouteProps = { - // TODO: Make sure this is `| null | undefined` (this is for global not-founds) - data: unknown -} +export type NotFoundRouteProps = Partial export class BaseRoute< in out TParentRoute extends AnyRoute = AnyRoute, From 35ec1177d9be0cd7d088a45aa7ed8640575a4b79 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Sat, 2 Aug 2025 19:25:13 +0900 Subject: [PATCH 3/9] test(react-router): add test verify notFoundRouteProps properly passes data Signed-off-by: leesb971204 --- .../react-router/tests/not-found.test.tsx | 118 +++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/packages/react-router/tests/not-found.test.tsx b/packages/react-router/tests/not-found.test.tsx index 9bcbdbd79c..974bbc9fae 100644 --- a/packages/react-router/tests/not-found.test.tsx +++ b/packages/react-router/tests/not-found.test.tsx @@ -9,8 +9,9 @@ import { createRootRoute, createRoute, createRouter, + notFound, } from '../src' -import type { RouterHistory } from '../src' +import type { NotFoundRouteProps, RouterHistory } from '../src' let history: RouterHistory @@ -123,3 +124,118 @@ test.each([ expect(notFoundComponent).toBeInTheDocument() }, ) + +test('defaultNotFoundComponent and notFoundComponent receives data props via spread operator', async () => { + const customData = { + message: 'Custom not found message', + } + + const DefaultNotFoundComponentWithProps = (props: NotFoundRouteProps) => ( +
+ {props.data.message} +
+ ) + + const NotFoundComponentWithProps = (props: NotFoundRouteProps) => ( +
+ {props.data.message} +
+ ) + + const rootRoute = createRootRoute({ + component: () => ( +
+

Root Component

+
+ + link to default not found route + + + link to not found route + +
+ +
+ ), + }) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () => ( +
+

Index Page

+
+ ), + }) + + const defaultNotFoundRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/default-not-found-route', + loader: () => { + throw notFound({ data: customData }) + }, + component: () => ( +
+ Should not render +
+ ), + }) + + const notFoundRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/not-found-route', + loader: () => { + throw notFound({ data: customData }) + }, + component: () => ( +
Should not render
+ ), + notFoundComponent: NotFoundComponentWithProps, + }) + + const router = createRouter({ + routeTree: rootRoute.addChildren([ + indexRoute, + defaultNotFoundRoute, + notFoundRoute, + ]), + history, + defaultNotFoundComponent: DefaultNotFoundComponentWithProps, + }) + + render() + await router.load() + await screen.findByTestId('root-component') + + const defaultNotFoundRouteLink = screen.getByTestId( + 'default-not-found-route-link', + ) + defaultNotFoundRouteLink.click() + + const defaultNotFoundComponent = await screen.findByTestId( + 'default-not-found-with-props', + {}, + { timeout: 1000 }, + ) + expect(defaultNotFoundComponent).toBeInTheDocument() + + const defaultNotFoundComponentMessage = await screen.findByTestId('message') + expect(defaultNotFoundComponentMessage).toHaveTextContent(customData.message) + + const notFoundRouteLink = screen.getByTestId('not-found-route-link') + notFoundRouteLink.click() + + const notFoundComponent = await screen.findByTestId( + 'not-found-with-props', + {}, + { timeout: 1000 }, + ) + expect(notFoundComponent).toBeInTheDocument() + + const errorMessageComponent = await screen.findByTestId('message') + expect(errorMessageComponent).toHaveTextContent(customData.message) +}) From 0b6cccb2096d685af22c7a85897756eae8d78cb9 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Sat, 2 Aug 2025 20:20:09 +0900 Subject: [PATCH 4/9] docs(react-router): correct type annotation for data in notFoundComponent Signed-off-by: leesb971204 --- docs/router/framework/react/guide/not-found-errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/framework/react/guide/not-found-errors.md b/docs/router/framework/react/guide/not-found-errors.md index acf45085b6..43cfe9a57d 100644 --- a/docs/router/framework/react/guide/not-found-errors.md +++ b/docs/router/framework/react/guide/not-found-errors.md @@ -240,7 +240,7 @@ export const Route = createFileRoute('/posts/$postId')({ }) return { post } }, - // `data: unknown` is passed to the component via the `data` option when calling `notFound` + // `data: any` is passed to the component via the `data` option when calling `notFound` notFoundComponent: ({ data }) => { // ❌ useLoaderData is not valid here: const { post } = Route.useLoaderData() From 2e28b590b3894b55260996e051c8313348e2f0c8 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Sun, 3 Aug 2025 21:08:09 +0900 Subject: [PATCH 5/9] refactor(router-core): enhance NotFoundRouteProps type with generics and isNotFound, routeId Signed-off-by: leesb971204 --- packages/router-core/src/route.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index 4b5d81ff9b..db3b211b56 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -14,7 +14,7 @@ import type { RouteMatch, } from './Matches' import type { RootRouteId } from './root' -import type { ParseRoute, RouteById, RoutePaths } from './routeInfo' +import type { ParseRoute, RouteById, RouteIds, RoutePaths } from './routeInfo' import type { AnyRouter, RegisteredRouter } from './router' import type { BuildLocationFn, NavigateFn } from './RouterProvider' import type { @@ -1322,7 +1322,12 @@ export type ErrorComponentProps = { info?: { componentStack: string } reset: () => void } -export type NotFoundRouteProps = Partial + +export type NotFoundRouteProps = { + data?: TData + isNotFound: boolean + routeId: RouteIds +} export class BaseRoute< in out TParentRoute extends AnyRoute = AnyRoute, From 3f98c6719d961fe2a35e1a6eb050d5b3adc5d404 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Wed, 13 Aug 2025 17:27:24 +0900 Subject: [PATCH 6/9] refactor(router-core): simplify NotFoundRouteProps type by removing generics Signed-off-by: leesb971204 --- packages/router-core/src/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/router-core/src/route.ts b/packages/router-core/src/route.ts index db3b211b56..2a7f74fdbb 100644 --- a/packages/router-core/src/route.ts +++ b/packages/router-core/src/route.ts @@ -1323,8 +1323,8 @@ export type ErrorComponentProps = { reset: () => void } -export type NotFoundRouteProps = { - data?: TData +export type NotFoundRouteProps = { + data?: unknown isNotFound: boolean routeId: RouteIds } From 337c1683f54a74a0ff6c6945933744d9f3e6c719 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Wed, 13 Aug 2025 17:27:42 +0900 Subject: [PATCH 7/9] test(react-router): improve notFoundComponent tests with type guard for data props Signed-off-by: leesb971204 --- .../react-router/tests/not-found.test.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/react-router/tests/not-found.test.tsx b/packages/react-router/tests/not-found.test.tsx index 974bbc9fae..ce8bdf1207 100644 --- a/packages/react-router/tests/not-found.test.tsx +++ b/packages/react-router/tests/not-found.test.tsx @@ -126,19 +126,19 @@ test.each([ ) test('defaultNotFoundComponent and notFoundComponent receives data props via spread operator', async () => { + const isCustomData = (data: unknown): data is typeof customData => { + return 'message' in (data as typeof customData) + } + const customData = { message: 'Custom not found message', } const DefaultNotFoundComponentWithProps = (props: NotFoundRouteProps) => (
- {props.data.message} -
- ) - - const NotFoundComponentWithProps = (props: NotFoundRouteProps) => ( -
- {props.data.message} + + {isCustomData(props.data) && {props.data.message}} +
) @@ -194,7 +194,13 @@ test('defaultNotFoundComponent and notFoundComponent receives data props via spr component: () => (
Should not render
), - notFoundComponent: NotFoundComponentWithProps, + notFoundComponent: (props) => ( +
+ + {isCustomData(props.data) && {props.data.message}} + +
+ ), }) const router = createRouter({ From 0fc4886871ad3c0ec49efc143d88f7f819725c20 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Wed, 13 Aug 2025 17:36:25 +0900 Subject: [PATCH 8/9] docs(router): revert type annotation for data in notFoundComponent Signed-off-by: leesb971204 --- docs/router/framework/react/guide/not-found-errors.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/router/framework/react/guide/not-found-errors.md b/docs/router/framework/react/guide/not-found-errors.md index 43cfe9a57d..acf45085b6 100644 --- a/docs/router/framework/react/guide/not-found-errors.md +++ b/docs/router/framework/react/guide/not-found-errors.md @@ -240,7 +240,7 @@ export const Route = createFileRoute('/posts/$postId')({ }) return { post } }, - // `data: any` is passed to the component via the `data` option when calling `notFound` + // `data: unknown` is passed to the component via the `data` option when calling `notFound` notFoundComponent: ({ data }) => { // ❌ useLoaderData is not valid here: const { post } = Route.useLoaderData() From 17e1384ef3c193c9e11b8ee5122c3d1736a0f9ee Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Wed, 13 Aug 2025 17:52:34 +0900 Subject: [PATCH 9/9] docs(router): add NotFoundComponent documentation and update router API reference Signed-off-by: leesb971204 --- docs/router/framework/react/api/router.md | 1 + .../api/router/notFoundComponentComponent.md | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 docs/router/framework/react/api/router/notFoundComponentComponent.md diff --git a/docs/router/framework/react/api/router.md b/docs/router/framework/react/api/router.md index ad3a58f851..e760a07378 100644 --- a/docs/router/framework/react/api/router.md +++ b/docs/router/framework/react/api/router.md @@ -32,6 +32,7 @@ title: Router API - [``](../router/linkComponent.md) - [``](../router/matchRouteComponent.md) - [``](../router/navigateComponent.md) + - [``](../router/notFoundComponentComponent.md) - [``](../router/outletComponent.md) - Hooks - [`useAwaited`](../router/useAwaitedHook.md) diff --git a/docs/router/framework/react/api/router/notFoundComponentComponent.md b/docs/router/framework/react/api/router/notFoundComponentComponent.md new file mode 100644 index 0000000000..63b9f93ae0 --- /dev/null +++ b/docs/router/framework/react/api/router/notFoundComponentComponent.md @@ -0,0 +1,36 @@ +--- +id: notFoundComponentComponent +title: NotFoundComponent component +--- + +The `NotFoundComponent` component is a component that renders when a not-found error occurs in a route. + +## NotFoundComponent props + +The `NotFoundComponent` component accepts the following props: + +### `props.data` prop + +- Type: `unknown` +- Optional +- Custom data that is passed to the `notFoundComponent` when the not-found error is handled +- This data comes from the `data` property of the `NotFoundError` object + +### `props.isNotFound` prop + +- Type: `boolean` +- Required +- A boolean value indicating whether the current state is a not-found error state +- This value is always `true` + +### `props.routeId` prop + +- Type: `RouteIds` +- Required +- The ID of the route that is attempting to handle the not-found error +- Must be one of the valid route IDs from the router's route tree + +## NotFoundComponent returns + +- Returns appropriate UI for not-found error situations +- Typically includes a "page not found" message along with links to go home or navigate to previous pages