T
TanStack2y ago
sensitive-blue

Migration from `NotFoundRoute` to `notFoundComponent`

I'm trying to follow the migration guide for NotFoundRoute in order to move on notFoundComponent. I have a "global" Not Found Error, which needs to be renderer with the RootRoute Layout. When I try to use notFoundComponent on rootRoute, the component is not displayed inside the Layout when it was well displayed with NotFoundRoute. This is very strange because the migration guide says : "When using NotFoundRoute, you can't use layouts. notFoundComponent can be used with layouts." Thank's for your effort the lib is very good !
12 Replies
variable-lime
variable-lime2y ago
Hi there! I made the pull request that adds these new APIs By "notFoundComponent can be used with layouts" I mean that if a not-found does happen, the component of the route is replaced with the notFoundComponent in whatever <Outlet /> is rendering it. This means that in the case of the root route (where there is no <Outlet />), the notFoundComponent is a direct replacement for component. Before, with NotFoundRoute, it sometimes would not display correctly when using layouts. If you want to persist your root layout, you could extract the layout elements to its own component and wrap both the route component and notFoundComponent in it. Alternatively, you can put the layout in InnerWrap (although you won't get the data loading functionality). Example for method 1: Before:
const rootRoute = createRootRouteWithContext({
notFoundComponent: () => {
return <>...</>
},
component: () => {
return (
<div>
<div>Layout stuff</div>
{/* the notFoundComponent will not get placed in this outlet */}
<Outlet />
</div>
);
},
});
const rootRoute = createRootRouteWithContext({
notFoundComponent: () => {
return <>...</>
},
component: () => {
return (
<div>
<div>Layout stuff</div>
{/* the notFoundComponent will not get placed in this outlet */}
<Outlet />
</div>
);
},
});
After:
function Layout({ children }) {
return (
<div>
<div>Layout stuff</div>
{children}
</div>
);
}

const rootRoute = createRootRouteWithContext({
notFoundComponent: () => {
return <Layout>...</Layout>
},
component: () => {
return (
<Layout>
<Outlet />
</Layout>
);
},
});
function Layout({ children }) {
return (
<div>
<div>Layout stuff</div>
{children}
</div>
);
}

const rootRoute = createRootRouteWithContext({
notFoundComponent: () => {
return <Layout>...</Layout>
},
component: () => {
return (
<Layout>
<Outlet />
</Layout>
);
},
});
sensitive-blue
sensitive-blueOP2y ago
Thank you for your quick response. Unfortunately, I am not convinced by these two solutions. I think it's important to be able to render notFoundComponent inside the Layout of the route it's declared
variable-lime
variable-lime2y ago
My rationale for not having the notFoundComponent render inside the layout of the current route (replacing the <Outlet /> inside it) is that the component that renders the outlet often uses loader data, which can throw not-found errors. It works the same for the root route in order to keep the behavior consistent between all types of routes. Maybe a layout option on route that wraps the route in a static, not-dependent-on-loader component could help this.
sensitive-blue
sensitive-blueOP2y ago
Ok it's a good point for route with loader. Where do you want to add layout option ? What do you think to something like this :
const rootRoute = createRootRoute({
component: Layout,
notFoundComponent: {() => <ErrorPage code={404} />,{ layout: true }}
})
const rootRoute = createRootRoute({
component: Layout,
notFoundComponent: {() => <ErrorPage code={404} />,{ layout: true }}
})
I would prefer something very close to component with option but hard to pass option
variable-lime
variable-lime2y ago
(an idea) like:
const rootRoute = createRootRoute({
/* If present, TSR will wrap all components in it. It should not use loader data */
/* staticLayout: ({ children }) => ReactNode */
staticLayout: Layout,
component: () => {
return <p>content</p>;
},
notFoundComponent: () => {
return <p>not found</p>;
},
});
const rootRoute = createRootRoute({
/* If present, TSR will wrap all components in it. It should not use loader data */
/* staticLayout: ({ children }) => ReactNode */
staticLayout: Layout,
component: () => {
return <p>content</p>;
},
notFoundComponent: () => {
return <p>not found</p>;
},
});
sensitive-blue
sensitive-blueOP2y ago
Why we can not use Layout with Outlet ?
variable-lime
variable-lime2y ago
It probably could, but I'm not too sure how to implement it
sensitive-blue
sensitive-blueOP2y ago
I think it's very important to make this possible. Changing the layout for this usecase do not appeared to be a good decision My RootRoute do not display something, i just use it to add my Layout
metropolitan-bronze
metropolitan-bronze2y ago
Unless I'm misunderstanding you, I have accomplished this with the following:
// __root.tsx

export const Route = createRootRouteWithContext<Context>()({
component: () => <RootComponent />,
notFoundComponent: () => <RootComponent notFound={<NotFound />} />,
});

interface RootComponentProps {
notFound?: React.ReactElement;
};

const RootComponent: React.FC<RootComponentProps> = ({notFound}) => {
let display = <Outlet />;
if (notFound)
display = notFound;
return (
<>
<NavBar />
{display}
<Footer />
</>
);
};
// __root.tsx

export const Route = createRootRouteWithContext<Context>()({
component: () => <RootComponent />,
notFoundComponent: () => <RootComponent notFound={<NotFound />} />,
});

interface RootComponentProps {
notFound?: React.ReactElement;
};

const RootComponent: React.FC<RootComponentProps> = ({notFound}) => {
let display = <Outlet />;
if (notFound)
display = notFound;
return (
<>
<NavBar />
{display}
<Footer />
</>
);
};
genetic-orange
genetic-orange2y ago
This can be trivially implemented with just a react component that accepts children?
sensitive-blue
sensitive-blueOP2y ago
export const Route = createRootRouteWithContext<Context>()({
component: () => <RootComponent />,
notFoundComponent: () => <RootComponent notFound={<NotFound />} />,
});

interface RootComponentProps {
notFound?: React.ReactElement;
};

const RootComponent = (props: RootComponentProps) => {

const { notFound } = props;

return (
<>
<NavBar />
{ notFound !== undefined ? notFound ?? <Outlet />}
<Footer />
</>
);
};
export const Route = createRootRouteWithContext<Context>()({
component: () => <RootComponent />,
notFoundComponent: () => <RootComponent notFound={<NotFound />} />,
});

interface RootComponentProps {
notFound?: React.ReactElement;
};

const RootComponent = (props: RootComponentProps) => {

const { notFound } = props;

return (
<>
<NavBar />
{ notFound !== undefined ? notFound ?? <Outlet />}
<Footer />
</>
);
};
something like that But yes this is well understood but i do not like mutate display
genetic-orange
genetic-orange2y ago
export const Route = createRootRouteWithContext<Context>()({
component: () => (
<RootComponent>
<Outlet />
</RootComponent>
),
notFoundComponent: () => (
<RootComponent>
<NotFound />
</RootComponent>
)
});

const RootComponent = (props: { children: React.ReactNode }) => {
return (
<>
<NavBar />
{props.children}
<Footer />
</>
)
}
export const Route = createRootRouteWithContext<Context>()({
component: () => (
<RootComponent>
<Outlet />
</RootComponent>
),
notFoundComponent: () => (
<RootComponent>
<NotFound />
</RootComponent>
)
});

const RootComponent = (props: { children: React.ReactNode }) => {
return (
<>
<NavBar />
{props.children}
<Footer />
</>
)
}
?

Did you find this page helpful?