import React from 'react'
import ReactDOM from 'react-dom/client'
import {
ErrorComponent,
Link,
Outlet,
RouterProvider,
createRootRouteWithContext,
createRoute,
createRouter,
useRouter,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import {
QueryClient,
QueryClientProvider,
useQueryErrorResetBoundary,
useSuspenseQuery,
} from '@tanstack/react-query'
import { NotFoundError, postQueryOptions, postsQueryOptions } from './posts'
import type { ErrorComponentProps } from '@tanstack/react-router'
import './styles.css'
const rootRoute = createRootRouteWithContext<{
queryClient: QueryClient
}>()({
component: RootComponent,
notFoundComponent: () => {
return (
<div>
<p>This is the notFoundComponent configured on root route</p>
<Link to="/">Start Over</Link>
</div>
)
},
})
function RootComponent() {
return (
<>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Home
</Link>{' '}
<Link
to="/posts"
activeProps={{
className: 'font-bold',
}}
>
Posts
</Link>{' '}
<Link
to="/route-a"
activeProps={{
className: 'font-bold',
}}
>
Pathless Layout
</Link>{' '}
<Link
// @ts-expect-error
to="/this-route-does-not-exist"
activeProps={{
className: 'font-bold',
}}
>
This Route Does Not Exist
</Link>
</div>
<hr />
<Outlet />
<ReactQueryDevtools buttonPosition="top-right" />
<TanStackRouterDevtools position="bottom-right" />
</>
)
}
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: IndexRouteComponent,
})
function IndexRouteComponent() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
}
const postsLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(postsQueryOptions),
}).lazy(() => import('./posts.lazy').then((d) => d.Route))
const postsIndexRoute = createRoute({
getParentRoute: () => postsLayoutRoute,
path: '/',
component: PostsIndexRouteComponent,
})
function PostsIndexRouteComponent() {
return <div>Select a post.</div>
}
const postRoute = createRoute({
getParentRoute: () => postsLayoutRoute,
path: '$postId',
errorComponent: PostErrorComponent,
loader: ({ context: { queryClient }, params: { postId } }) =>
queryClient.ensureQueryData(postQueryOptions(postId)),
component: PostRouteComponent,
})
function PostErrorComponent({ error }: ErrorComponentProps) {
const router = useRouter()
if (error instanceof NotFoundError) {
return <div>{error.message}</div>
}
const queryErrorResetBoundary = useQueryErrorResetBoundary()
React.useEffect(() => {
queryErrorResetBoundary.reset()
}, [queryErrorResetBoundary])
return (
<div>
<button
onClick={() => {
router.invalidate()
}}
>
retry
</button>
<ErrorComponent error={error} />
</div>
)
}
function PostRouteComponent() {
const { postId } = postRoute.useParams()
const postQuery = useSuspenseQuery(postQueryOptions(postId))
const post = postQuery.data
return (
<div className="space-y-2">
<h4 className="text-xl font-bold underline">{post.title}</h4>
<div className="text-sm">{post.body}</div>
</div>
)
}
const pathlessLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: '_pathlessLayout',
component: PathlessLayoutComponent,
})
function PathlessLayoutComponent() {
return (
<div className="p-2">
<div className="border-b">I'm a pathless layout</div>
<div>
<Outlet />
</div>
</div>
)
}
const nestedPathlessLayoutRoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
id: '_nestedPathlessLayout',
component: Layout2Component,
})
function Layout2Component() {
return (
<div>
<div>I'm a nested pathless layout</div>
<div className="flex gap-2 border-b">
<Link
to="/route-a"
activeProps={{
className: 'font-bold',
}}
>
Go to route A
</Link>
<Link
to="/route-b"
activeProps={{
className: 'font-bold',
}}
>
Go to route B
</Link>
</div>
<div>
<Outlet />
</div>
</div>
)
}
const pathlessLayoutARoute = createRoute({
getParentRoute: () => nestedPathlessLayoutRoute,
path: '/route-a',
component: PathlessLayoutAComponent,
})
function PathlessLayoutAComponent() {
return <div>I'm layout A!</div>
}
const pathlessLayoutBRoute = createRoute({
getParentRoute: () => nestedPathlessLayoutRoute,
path: '/route-b',
component: PathlessLayoutBComponent,
})
function PathlessLayoutBComponent() {
return <div>I'm layout B!</div>
}
const routeTree = rootRoute.addChildren([
postsLayoutRoute.addChildren([postRoute, postsIndexRoute]),
pathlessLayoutRoute.addChildren([
nestedPathlessLayoutRoute.addChildren([
pathlessLayoutARoute,
pathlessLayoutBRoute,
]),
]),
indexRoute,
])
const queryClient = new QueryClient()
// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: 'intent',
// Since we're using React Query, we don't want loader calls to ever be stale
// This will ensure that the loader is always called when the route is preloaded or visited
defaultPreloadStaleTime: 0,
scrollRestoration: true,
context: {
queryClient,
},
})
// Register things for typesafety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('app')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
)
}
import React from 'react'
import ReactDOM from 'react-dom/client'
import {
ErrorComponent,
Link,
Outlet,
RouterProvider,
createRootRouteWithContext,
createRoute,
createRouter,
useRouter,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import {
QueryClient,
QueryClientProvider,
useQueryErrorResetBoundary,
useSuspenseQuery,
} from '@tanstack/react-query'
import { NotFoundError, postQueryOptions, postsQueryOptions } from './posts'
import type { ErrorComponentProps } from '@tanstack/react-router'
import './styles.css'
const rootRoute = createRootRouteWithContext<{
queryClient: QueryClient
}>()({
component: RootComponent,
notFoundComponent: () => {
return (
<div>
<p>This is the notFoundComponent configured on root route</p>
<Link to="/">Start Over</Link>
</div>
)
},
})
function RootComponent() {
return (
<>
<div className="p-2 flex gap-2 text-lg">
<Link
to="/"
activeProps={{
className: 'font-bold',
}}
activeOptions={{ exact: true }}
>
Home
</Link>{' '}
<Link
to="/posts"
activeProps={{
className: 'font-bold',
}}
>
Posts
</Link>{' '}
<Link
to="/route-a"
activeProps={{
className: 'font-bold',
}}
>
Pathless Layout
</Link>{' '}
<Link
// @ts-expect-error
to="/this-route-does-not-exist"
activeProps={{
className: 'font-bold',
}}
>
This Route Does Not Exist
</Link>
</div>
<hr />
<Outlet />
<ReactQueryDevtools buttonPosition="top-right" />
<TanStackRouterDevtools position="bottom-right" />
</>
)
}
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: IndexRouteComponent,
})
function IndexRouteComponent() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
)
}
const postsLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
path: 'posts',
loader: ({ context: { queryClient } }) =>
queryClient.ensureQueryData(postsQueryOptions),
}).lazy(() => import('./posts.lazy').then((d) => d.Route))
const postsIndexRoute = createRoute({
getParentRoute: () => postsLayoutRoute,
path: '/',
component: PostsIndexRouteComponent,
})
function PostsIndexRouteComponent() {
return <div>Select a post.</div>
}
const postRoute = createRoute({
getParentRoute: () => postsLayoutRoute,
path: '$postId',
errorComponent: PostErrorComponent,
loader: ({ context: { queryClient }, params: { postId } }) =>
queryClient.ensureQueryData(postQueryOptions(postId)),
component: PostRouteComponent,
})
function PostErrorComponent({ error }: ErrorComponentProps) {
const router = useRouter()
if (error instanceof NotFoundError) {
return <div>{error.message}</div>
}
const queryErrorResetBoundary = useQueryErrorResetBoundary()
React.useEffect(() => {
queryErrorResetBoundary.reset()
}, [queryErrorResetBoundary])
return (
<div>
<button
onClick={() => {
router.invalidate()
}}
>
retry
</button>
<ErrorComponent error={error} />
</div>
)
}
function PostRouteComponent() {
const { postId } = postRoute.useParams()
const postQuery = useSuspenseQuery(postQueryOptions(postId))
const post = postQuery.data
return (
<div className="space-y-2">
<h4 className="text-xl font-bold underline">{post.title}</h4>
<div className="text-sm">{post.body}</div>
</div>
)
}
const pathlessLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: '_pathlessLayout',
component: PathlessLayoutComponent,
})
function PathlessLayoutComponent() {
return (
<div className="p-2">
<div className="border-b">I'm a pathless layout</div>
<div>
<Outlet />
</div>
</div>
)
}
const nestedPathlessLayoutRoute = createRoute({
getParentRoute: () => pathlessLayoutRoute,
id: '_nestedPathlessLayout',
component: Layout2Component,
})
function Layout2Component() {
return (
<div>
<div>I'm a nested pathless layout</div>
<div className="flex gap-2 border-b">
<Link
to="/route-a"
activeProps={{
className: 'font-bold',
}}
>
Go to route A
</Link>
<Link
to="/route-b"
activeProps={{
className: 'font-bold',
}}
>
Go to route B
</Link>
</div>
<div>
<Outlet />
</div>
</div>
)
}
const pathlessLayoutARoute = createRoute({
getParentRoute: () => nestedPathlessLayoutRoute,
path: '/route-a',
component: PathlessLayoutAComponent,
})
function PathlessLayoutAComponent() {
return <div>I'm layout A!</div>
}
const pathlessLayoutBRoute = createRoute({
getParentRoute: () => nestedPathlessLayoutRoute,
path: '/route-b',
component: PathlessLayoutBComponent,
})
function PathlessLayoutBComponent() {
return <div>I'm layout B!</div>
}
const routeTree = rootRoute.addChildren([
postsLayoutRoute.addChildren([postRoute, postsIndexRoute]),
pathlessLayoutRoute.addChildren([
nestedPathlessLayoutRoute.addChildren([
pathlessLayoutARoute,
pathlessLayoutBRoute,
]),
]),
indexRoute,
])
const queryClient = new QueryClient()
// Set up a Router instance
const router = createRouter({
routeTree,
defaultPreload: 'intent',
// Since we're using React Query, we don't want loader calls to ever be stale
// This will ensure that the loader is always called when the route is preloaded or visited
defaultPreloadStaleTime: 0,
scrollRestoration: true,
context: {
queryClient,
},
})
// Register things for typesafety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('app')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>,
)
}
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.