Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
29 changes: 17 additions & 12 deletions packages/react-router/src/renderRouteNotFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,25 @@ export function renderRouteNotFound(
route: AnyRoute,
data: any,
) {
if (!route.options.notFoundComponent) {
if (router.options.defaultNotFoundComponent) {
return <router.options.defaultNotFoundComponent {...data} />
}
let routeWithNotFound: AnyRoute | undefined = route
while (routeWithNotFound && !routeWithNotFound.options.notFoundComponent) {
routeWithNotFound = routeWithNotFound.parentRoute
}

if (routeWithNotFound?.options.notFoundComponent) {
return <routeWithNotFound.options.notFoundComponent {...data} />
}

if (process.env.NODE_ENV === 'development') {
warning(
route.options.notFoundComponent,
`A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<p>Not Found</p>)`,
)
}
if (router.options.defaultNotFoundComponent) {
return <router.options.defaultNotFoundComponent {...data} />
}

return <DefaultGlobalNotFound />
if (process.env.NODE_ENV === 'development') {
warning(
false,
`A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<p>Not Found</p>)`,
)
}

return <route.options.notFoundComponent {...data} />
return <DefaultGlobalNotFound />
}
72 changes: 72 additions & 0 deletions packages/react-router/tests/not-found.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,75 @@ test('defaultNotFoundComponent and notFoundComponent receives data props via spr
const errorMessageComponent = await screen.findByTestId('message')
expect(errorMessageComponent).toHaveTextContent(customData.message)
})

test('pathless layout route notFoundComponent is used when child has none (issue #6351)', async () => {
const rootRoute = createRootRoute({
component: () => (
<div data-testid="root">
<Outlet />
</div>
),
})

const authenticatedRoute = createRoute({
getParentRoute: () => rootRoute,
id: '_authenticated',
component: () => (
<div data-testid="authenticated-layout">
<Outlet />
</div>
),
notFoundComponent: () => (
<div data-testid="authenticated-not-found">
Authenticated Not Found
</div>
),
})

const agentsRoute = createRoute({
getParentRoute: () => authenticatedRoute,
path: 'agents',
component: () => (
<div data-testid="agents-layout">
<Outlet />
</div>
),
})

const agentsIndexRoute = createRoute({
getParentRoute: () => agentsRoute,
path: '/',
component: () => <div data-testid="agents-index">Agents Index</div>,
})

const skillAgentRoute = createRoute({
getParentRoute: () => agentsRoute,
path: 'skill-agent',
component: () => <div data-testid="skill-agent">Skill Agent</div>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([
authenticatedRoute.addChildren([
agentsRoute.addChildren([agentsIndexRoute, skillAgentRoute]),
]),
]),
history,
defaultNotFoundComponent: () => (
<div data-testid="default-not-found">Default Not Found</div>
),
})

window.history.replaceState(null, '', '/agents/non-existent')

render(<RouterProvider router={router} />)
await router.load()

const notFound = await screen.findByTestId(
'authenticated-not-found',
{},
{ timeout: 1000 },
)
expect(notFound).toBeInTheDocument()
expect(screen.queryByTestId('default-not-found')).not.toBeInTheDocument()
})