Skip to content

Commit

Permalink
feat: support errorComponent and pendingComponent code splitting
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Jan 10, 2024
1 parent 17ac14c commit d094028
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 85 deletions.
85 changes: 68 additions & 17 deletions examples/react/basic-file-based-codesplitting/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import { Route as rootRoute } from './routes/__root'
import { Route as LayoutImport } from './routes/_layout'
import { Route as IndexImport } from './routes/index'
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId/route'
import { Route as PostsIndexImport } from './routes/posts.index'
import { Route as LayoutLayoutBImport } from './routes/_layout/layout-b'
import { Route as LayoutLayoutAImport } from './routes/_layout/layout-a'
import { Route as PostsPostIdDeepImport } from './routes/posts_.$postId.deep'

const PostsComponentImport = new FileRoute('/posts').createRoute()
const PostsPostIdDeepComponentImport = new FileRoute(
'/posts/$postId/deep',
).createRoute()
const LayoutLayoutBTestComponentImport = new FileRoute(
'/_layout/layout-b/test',
).createRoute()

const PostsComponentRoute = PostsComponentImport.update({
path: '/posts',
Expand All @@ -35,8 +41,8 @@ const IndexRoute = IndexImport.update({
} as any)

const PostsPostIdRouteRoute = PostsPostIdRouteImport.update({
path: '/posts/$postId',
getParentRoute: () => rootRoute,
path: '/$postId',
getParentRoute: () => PostsComponentRoute,
} as any)
.updateLoader({
loader: lazyFn(() => import('./routes/posts.$postId/loader'), 'loader'),
Expand All @@ -46,8 +52,17 @@ const PostsPostIdRouteRoute = PostsPostIdRouteImport.update({
() => import('./routes/posts.$postId/component'),
'component',
),
errorComponent: lazyRouteComponent(
() => import('./routes/posts.$postId/errorComponent'),
'errorComponent',
),
})

const PostsIndexRoute = PostsIndexImport.update({
path: '/',
getParentRoute: () => PostsComponentRoute,
} as any)

const LayoutLayoutBRoute = LayoutLayoutBImport.update({
path: '/layout-b',
getParentRoute: () => LayoutRoute,
Expand All @@ -58,11 +73,38 @@ const LayoutLayoutARoute = LayoutLayoutAImport.update({
getParentRoute: () => LayoutRoute,
} as any)

const PostsPostIdDeepRoute = PostsPostIdDeepImport.update({
const PostsPostIdDeepComponentRoute = PostsPostIdDeepComponentImport.update({
path: '/posts/$postId/deep',
getParentRoute: () => rootRoute,
} as any)
.updateLoader({
loader: lazyFn(
() => import('./routes/posts_.$postId.deep.loader'),
'loader',
),
})
.update({
component: lazyRouteComponent(
() => import('./routes/posts_.$postId.deep.component'),
'component',
),
errorComponent: lazyRouteComponent(
() => import('./routes/posts_.$postId.deep.errorComponent'),
'errorComponent',
),
})

const LayoutLayoutBTestComponentRoute = LayoutLayoutBTestComponentImport.update(
{
path: '/test',
getParentRoute: () => LayoutLayoutBRoute,
} as any,
).update({
component: lazyRouteComponent(
() => import('./routes/_layout/layout-b.test.component'),
'component',
),
})
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
Expand All @@ -75,31 +117,40 @@ declare module '@tanstack/react-router' {
}
'/_layout/layout-a': {
preLoaderRoute: typeof LayoutLayoutAImport
parentRoute: typeof LayoutRoute
parentRoute: typeof LayoutImport
}
'/_layout/layout-b': {
preLoaderRoute: typeof LayoutLayoutBImport
parentRoute: typeof LayoutRoute
parentRoute: typeof LayoutImport
}
'/posts': {
preLoaderRoute: typeof PostsComponentImport
parentRoute: typeof rootRoute
}
'/posts/': {
preLoaderRoute: typeof PostsIndexImport
parentRoute: typeof PostsComponentImport
}
'/posts/$postId': {
preLoaderRoute: typeof PostsPostIdRouteImport
parentRoute: typeof rootRoute
parentRoute: typeof PostsComponentImport
}
'/posts_/$postId/deep': {
preLoaderRoute: typeof PostsPostIdDeepImport
parentRoute: typeof rootRoute
'/_layout/layout-b/test': {
preLoaderRoute: typeof LayoutLayoutBTestComponentImport
parentRoute: typeof LayoutLayoutBImport
}
'/posts': {
preLoaderRoute: typeof PostsComponentImport
'/posts/$postId/deep': {
preLoaderRoute: typeof PostsPostIdDeepComponentImport
parentRoute: typeof rootRoute
}
}
}

export const routeTree = rootRoute.addChildren([
IndexRoute,
LayoutRoute.addChildren([LayoutLayoutARoute, LayoutLayoutBRoute]),
PostsPostIdRouteRoute,
PostsPostIdDeepRoute,
PostsComponentRoute,
LayoutRoute.addChildren([
LayoutLayoutARoute,
LayoutLayoutBRoute.addChildren([LayoutLayoutBTestComponentRoute]),
]),
PostsComponentRoute.addChildren([PostsIndexRoute, PostsPostIdRouteRoute]),
PostsPostIdDeepComponentRoute,
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react'
import { ErrorComponent, ErrorRouteProps } from '@tanstack/react-router'
import { PostNotFoundError } from '../../posts'

export const errorComponent = function PostErrorComponent({
error,
}: ErrorRouteProps) {
if (error instanceof PostNotFoundError) {
return <div>{error.message}</div>
}

return <ErrorComponent error={error} />
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import * as React from 'react'
import {
ErrorComponent,
ErrorRouteProps,
FileRoute,
} from '@tanstack/react-router'
import { PostNotFoundError } from '../../posts'
import { FileRoute } from '@tanstack/react-router'

export const Route = new FileRoute('/posts/$postId').createRoute({
errorComponent: PostErrorComponent,
loaderDeps: () => ({
test: 'tanner' as const,
}),
loader: () => 'tanner',
})

export function PostErrorComponent({ error }: ErrorRouteProps) {
if (error instanceof PostNotFoundError) {
return <div>{error.message}</div>
}

return <ErrorComponent error={error} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react'
import { Link, RouteApi } from '@tanstack/react-router'

const api = new RouteApi({ id: '/posts/$postId/deep' })

export const component = function PostDeepComponent() {
const post = api.useLoaderData()

return (
<div className="p-2 space-y-2">
<Link
to="/posts"
className="block py-1 text-blue-800 hover:text-blue-600"
>
← All Posts
</Link>
<h4 className="text-xl font-bold underline">{post.title}</h4>
<div className="text-sm">{post.body}</div>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { errorComponent } from './posts.$postId/errorComponent'

export { errorComponent }
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FileRouteLoader } from '@tanstack/react-router'
import { fetchPost } from '../posts'

export const loader = FileRouteLoader('/posts/$postId/deep')(
async ({ params: { postId } }) => fetchPost(postId),
)

This file was deleted.

98 changes: 98 additions & 0 deletions packages/react-router/src/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,104 @@ export type RouteConstraints = {
TRouteTree: AnyRoute
}

// TODO: This is part of a future APi to move away from classes and
// towards a more functional API. It's not ready yet.

// type RouteApiInstance<
// TId extends RouteIds<RegisteredRouter['routeTree']>,
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
// TFullSearchSchema extends Record<
// string,
// any
// > = TRoute['types']['fullSearchSchema'],
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
// TLoaderData extends any = TRoute['types']['loaderData'],
// > = {
// id: TId
// useMatch: <TSelected = TAllContext>(opts?: {
// select?: (s: TAllContext) => TSelected
// }) => TSelected

// useRouteContext: <TSelected = TAllContext>(opts?: {
// select?: (s: TAllContext) => TSelected
// }) => TSelected

// useSearch: <TSelected = TFullSearchSchema>(opts?: {
// select?: (s: TFullSearchSchema) => TSelected
// }) => TSelected

// useParams: <TSelected = TAllParams>(opts?: {
// select?: (s: TAllParams) => TSelected
// }) => TSelected

// useLoaderDeps: <TSelected = TLoaderDeps>(opts?: {
// select?: (s: TLoaderDeps) => TSelected
// }) => TSelected

// useLoaderData: <TSelected = TLoaderData>(opts?: {
// select?: (s: TLoaderData) => TSelected
// }) => TSelected
// }

// export function RouteApi_v2<
// TId extends RouteIds<RegisteredRouter['routeTree']>,
// TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
// TFullSearchSchema extends Record<
// string,
// any
// > = TRoute['types']['fullSearchSchema'],
// TAllParams extends AnyPathParams = TRoute['types']['allParams'],
// TAllContext extends Record<string, any> = TRoute['types']['allContext'],
// TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
// TLoaderData extends any = TRoute['types']['loaderData'],
// >({
// id,
// }: {
// id: TId
// }): RouteApiInstance<
// TId,
// TRoute,
// TFullSearchSchema,
// TAllParams,
// TAllContext,
// TLoaderDeps,
// TLoaderData
// > {
// return {
// id,

// useMatch: (opts) => {
// return useMatch({ ...opts, from: id })
// },

// useRouteContext: (opts) => {
// return useMatch({
// ...opts,
// from: id,
// select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
// } as any)
// },

// useSearch: (opts) => {
// return useSearch({ ...opts, from: id } as any)
// },

// useParams: (opts) => {
// return useParams({ ...opts, from: id } as any)
// },

// useLoaderDeps: (opts) => {
// return useLoaderDeps({ ...opts, from: id } as any) as any
// },

// useLoaderData: (opts) => {
// return useLoaderData({ ...opts, from: id } as any) as any
// },
// }
// }

export class RouteApi<
TId extends RouteIds<RegisteredRouter['routeTree']>,
TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
Expand Down
Loading

0 comments on commit d094028

Please sign in to comment.