T
TanStack•2mo ago
correct-apricot

Specify the `component` dynamically, based on route context

hii i have a use case in which i need to decide what component to render after fetching the data for the route. how do you think it should be implemented? currently, i'm thinking on using a code-based splitting approach. something like
.lazy(() => routeContext.first
? import('first.lazy')...
: import('second.lazy')...
)
.lazy(() => routeContext.first
? import('first.lazy')...
: import('second.lazy')...
)
but using it doesn't look right for the file-based routes project 🤔
17 Replies
optimistic-gold
optimistic-gold•2mo ago
If its based on the loaderData, then you can rely on Suspense to fetch the component.
import React from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { getLoaderData } from '~/stuff'

const ComponentA = React.lazy(() => import('~/components/a'))
const ComponentB = React.lazy(() => import('~/components/b'))

export const Route = createFileRoute('/users/profile')({
loader: () => getLoaderData(),
component: RouteComponent
})

function RouteComponent() {
const loaderData = Route.useLoaderData();

return (
loaderData === (Math.random() > 0.5)
? <ComponentA />
: <ComponentB />
)
}
import React from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { getLoaderData } from '~/stuff'

const ComponentA = React.lazy(() => import('~/components/a'))
const ComponentB = React.lazy(() => import('~/components/b'))

export const Route = createFileRoute('/users/profile')({
loader: () => getLoaderData(),
component: RouteComponent
})

function RouteComponent() {
const loaderData = Route.useLoaderData();

return (
loaderData === (Math.random() > 0.5)
? <ComponentA />
: <ComponentB />
)
}
The only issue I see if that, your component would be getting loaded-in only have the loader has completed, so there is an inherent waterfall. If the component are SUPER different from one another, I'd reckon it may even serve to have a different page altogether. Using the loaderData to "build" the page is also not a bad idea.
import React from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { getLoaderData } from '~/stuff'
import { Grid, Col, SkeletonLoader } from '~/component-library'

const AdminA = React.lazy(() => import('~/widgets/admin'))
const OrdersWidget = React.lazy(() => import('~/wdigets/orders'))

export const Route = createFileRoute('/dashboard')({
loader: () => getLoaderData(),
component: RouteComponent
})

function RouteComponent() {
const claims = Route.useLoaderData({ select: (s) => s.claims });

return (
<Grid>
{claims.includes('orders') ? (
<React.Suspense fallback={<SkeletonLoader />}>
<OrdersWidget />
</React.Suspense>
) : null}
{claims.includes('admin') ? (
<React.Suspense fallback={<SkeletonLoader />}>
<AdminWidget />
</React.Suspense>
) : null}
</Grid>
)
}
import React from 'react'
import { createFileRoute } from '@tanstack/react-router'
import { getLoaderData } from '~/stuff'
import { Grid, Col, SkeletonLoader } from '~/component-library'

const AdminA = React.lazy(() => import('~/widgets/admin'))
const OrdersWidget = React.lazy(() => import('~/wdigets/orders'))

export const Route = createFileRoute('/dashboard')({
loader: () => getLoaderData(),
component: RouteComponent
})

function RouteComponent() {
const claims = Route.useLoaderData({ select: (s) => s.claims });

return (
<Grid>
{claims.includes('orders') ? (
<React.Suspense fallback={<SkeletonLoader />}>
<OrdersWidget />
</React.Suspense>
) : null}
{claims.includes('admin') ? (
<React.Suspense fallback={<SkeletonLoader />}>
<AdminWidget />
</React.Suspense>
) : null}
</Grid>
)
}
conscious-sapphire
conscious-sapphire•2mo ago
If you want to make preloading possible, you can probably call the imports in the loader
const adminImport = () => import('~/widgets/admin')
const AdminA = React.lazy(adminImport)
const ordersImport = () => import('~/wdigets/orders')
const OrdersWidget = React.lazy(ordersImport)

export const Route = createFileRoute('/dashboard')({
loader: async () => {
const condition = await getLoaderData()
if (foo) await adminImport()
else await ordersImport()
return condition
},
component: RouteComponent
})
const adminImport = () => import('~/widgets/admin')
const AdminA = React.lazy(adminImport)
const ordersImport = () => import('~/wdigets/orders')
const OrdersWidget = React.lazy(ordersImport)

export const Route = createFileRoute('/dashboard')({
loader: async () => {
const condition = await getLoaderData()
if (foo) await adminImport()
else await ordersImport()
return condition
},
component: RouteComponent
})
correct-apricot
correct-apricotOP•2mo ago
thanks for your response! i think i might need to provide a bit more context my goal is to have different types of content on the same URL pattern. for example, Content1.tsx or Content2.tsx could display /posts/$postId, depending on the backend response in the loader but here it gets a bit more difficult: i would like the page to properly SSR* without streaming, which is where React.lazy would not work (because renderToString doesn't support Suspense). my intuition suggests that i might need to have separate routes for Content1.tsx and Content2.tsx, and somehow silently redirect (without changing the URL) to them from the data fetching route while passing the state to the context. do you think that's possible? *for SSR i'm considering migrating to TanStack Start
wise-white
wise-white•2mo ago
why split the route component then at all?
correct-apricot
correct-apricotOP•2mo ago
because Content1.tsx and Content2.tsx have dependencies that are not related. splitting them allows for a smaller bundle size for that route
wise-white
wise-white•2mo ago
but that's only relevant on the client then? just trying to understand the big picture here
correct-apricot
correct-apricotOP•2mo ago
tbh i'm not sure sure what you're referring to... the server needs to know which chunks to include for that page in the <head> of the SSR response, i guess
wise-white
wise-white•2mo ago
that's only for preloading in any case, if you do manual splitting, server wont know about that and thus cannot inject the modulepreloads appropriately
correct-apricot
correct-apricotOP•2mo ago
what about stylesheets?
wise-white
wise-white•2mo ago
wont work with manual splitting either with automatic splitting, yes how could the server possibly know which things you render before you render them
correct-apricot
correct-apricotOP•2mo ago
need to have separate routes and somehow silently redirect (without changing the URL) to them
i guess if this was possible it could work
wise-white
wise-white•2mo ago
thats not possible redirecting without url change you could manually link the styles in the head function based on loader data but this requires using importing the styles using ?url
correct-apricot
correct-apricotOP•2mo ago
that's unfortunate :( i'm currently using Vike, and there's a method render(url), which allows you to render another route, while preserving the current URL: https://vike.dev/render however, it's still not perfect since there's no way to pass data to the next route :D anyway, thanks for the help!
wise-white
wise-white•2mo ago
where does this requirement come from btw? to not bind layout to the URL
correct-apricot
correct-apricotOP•2mo ago
i'm building something like a file page on Github. there are multiple ways of displaying a file: highlighted code, 3D model, CSV table, diagrams, etc. i imagine all of that as different layouts, with their own dependencies
GitHub Docs
Working with non-code files - GitHub Docs
GitHub supports rendering and diffing in a number of non-code file formats.
wise-white
wise-white•2mo ago
and does GitHub deliver different stylesheets and js bundles depending on the file type?
correct-apricot
correct-apricotOP•2mo ago
idk i haven't really checked because it's not like Github is known for having a snappy frontend experience lol i only know that they load a 3D model viewer as an iframe

Did you find this page helpful?