<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][.gitignore]" value="node_modules
.DS_Store
dist
dist-ssr
*.local

/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
">
<input type="hidden" name="project[files][README.md]" value="# TanStack Router - React Query Example

An example demonstrating integration between TanStack Router and TanStack Query.

- [TanStack Router Docs](https://tanstack.com/router)
- [TanStack Query Docs](https://tanstack.com/query)

## Start a new project based on this example

To start a new project based on this example, run:

```sh
npx gitpick TanStack/router/tree/main/examples/react/basic-react-query basic-react-query
```

## Getting Started

Install dependencies:

```sh
pnpm install
```

Start the development server:

```sh
pnpm dev
```

## Build

Build for production:

```sh
pnpm build
```

## About This Example

This example demonstrates:

- Integrating TanStack Query with TanStack Router
- Data fetching with queries
- Route-level data loading
- Cache management
- Prefetching data on route navigation
">
<input type="hidden" name="project[files][index.html]" value="&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
    &lt;title&gt;Vite App&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot; src=&quot;/src/main.tsx&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;tanstack-router-react-example-react-query&quot;,&quot;private&quot;:true,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;vite --port 3000&quot;,&quot;build&quot;:&quot;vite build &amp;&amp; tsc --noEmit&quot;,&quot;preview&quot;:&quot;vite preview&quot;,&quot;start&quot;:&quot;vite&quot;},&quot;dependencies&quot;:{&quot;@tailwindcss/vite&quot;:&quot;^4.1.18&quot;,&quot;@tanstack/react-query&quot;:&quot;^5.90.0&quot;,&quot;@tanstack/react-query-devtools&quot;:&quot;^5.90.0&quot;,&quot;@tanstack/react-router&quot;:&quot;https://pkg.pr.new/TanStack/router/@tanstack/react-router@e1c34ffdf31d70311e79f524a1c35a5fa644ea03&quot;,&quot;@tanstack/react-router-devtools&quot;:&quot;https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@e1c34ffdf31d70311e79f524a1c35a5fa644ea03&quot;,&quot;react&quot;:&quot;^19.0.0&quot;,&quot;react-dom&quot;:&quot;^19.0.0&quot;,&quot;redaxios&quot;:&quot;^0.5.1&quot;,&quot;tailwindcss&quot;:&quot;^4.1.18&quot;},&quot;devDependencies&quot;:{&quot;@types/react&quot;:&quot;^19.0.8&quot;,&quot;@types/react-dom&quot;:&quot;^19.0.3&quot;,&quot;@vitejs/plugin-react&quot;:&quot;^4.3.4&quot;,&quot;typescript&quot;:&quot;^5.7.2&quot;,&quot;vite&quot;:&quot;^7.3.1&quot;}}">
<input type="hidden" name="project[files][tsconfig.json]" value="{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;target&quot;: &quot;ESNext&quot;,
    &quot;moduleResolution&quot;: &quot;Bundler&quot;,
    &quot;module&quot;: &quot;ESNext&quot;,
    &quot;lib&quot;: [&quot;DOM&quot;, &quot;DOM.Iterable&quot;, &quot;ES2022&quot;],
    &quot;skipLibCheck&quot;: true
  }
}
">
<input type="hidden" name="project[files][vite.config.js]" value="import { defineConfig } from &#39;vite&#39;
import react from &#39;@vitejs/plugin-react&#39;
import tailwindcss from &#39;@tailwindcss/vite&#39;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [tailwindcss(), react()],
})
">
<input type="hidden" name="project[files][.vscode/settings.json]" value="{
  &quot;files.watcherExclude&quot;: {
    &quot;**/routeTree.gen.ts&quot;: true
  },
  &quot;search.exclude&quot;: {
    &quot;**/routeTree.gen.ts&quot;: true
  },
  &quot;files.readonlyInclude&quot;: {
    &quot;**/routeTree.gen.ts&quot;: true
  }
}
">
<input type="hidden" name="project[files][src/main.tsx]" value="import React from &#39;react&#39;
import ReactDOM from &#39;react-dom/client&#39;
import {
  ErrorComponent,
  Link,
  Outlet,
  RouterProvider,
  createRootRouteWithContext,
  createRoute,
  createRouter,
  useRouter,
} from &#39;@tanstack/react-router&#39;
import { TanStackRouterDevtools } from &#39;@tanstack/react-router-devtools&#39;
import { ReactQueryDevtools } from &#39;@tanstack/react-query-devtools&#39;
import {
  QueryClient,
  QueryClientProvider,
  useQueryErrorResetBoundary,
  useSuspenseQuery,
} from &#39;@tanstack/react-query&#39;
import { NotFoundError, postQueryOptions, postsQueryOptions } from &#39;./posts&#39;
import type { ErrorComponentProps } from &#39;@tanstack/react-router&#39;
import &#39;./styles.css&#39;

const rootRoute = createRootRouteWithContext&lt;{
  queryClient: QueryClient
}&gt;()({
  component: RootComponent,
  notFoundComponent: () =&gt; {
    return (
      &lt;div&gt;
        &lt;p&gt;This is the notFoundComponent configured on root route&lt;/p&gt;
        &lt;Link to=&quot;/&quot;&gt;Start Over&lt;/Link&gt;
      &lt;/div&gt;
    )
  },
})

function RootComponent() {
  return (
    &lt;&gt;
      &lt;div className=&quot;p-2 flex gap-2 text-lg&quot;&gt;
        &lt;Link
          to=&quot;/&quot;
          activeProps={{
            className: &#39;font-bold&#39;,
          }}
          activeOptions={{ exact: true }}
        &gt;
          Home
        &lt;/Link&gt;{&#39; &#39;}
        &lt;Link
          to=&quot;/posts&quot;
          activeProps={{
            className: &#39;font-bold&#39;,
          }}
        &gt;
          Posts
        &lt;/Link&gt;{&#39; &#39;}
        &lt;Link
          to=&quot;/route-a&quot;
          activeProps={{
            className: &#39;font-bold&#39;,
          }}
        &gt;
          Pathless Layout
        &lt;/Link&gt;{&#39; &#39;}
        &lt;Link
          // @ts-expect-error
          to=&quot;/this-route-does-not-exist&quot;
          activeProps={{
            className: &#39;font-bold&#39;,
          }}
        &gt;
          This Route Does Not Exist
        &lt;/Link&gt;
      &lt;/div&gt;
      &lt;hr /&gt;
      &lt;Outlet /&gt;
      &lt;ReactQueryDevtools buttonPosition=&quot;top-right&quot; /&gt;
      &lt;TanStackRouterDevtools position=&quot;bottom-right&quot; /&gt;
    &lt;/&gt;
  )
}

const indexRoute = createRoute({
  getParentRoute: () =&gt; rootRoute,
  path: &#39;/&#39;,
  component: IndexRouteComponent,
})

function IndexRouteComponent() {
  return (
    &lt;div className=&quot;p-2&quot;&gt;
      &lt;h3&gt;Welcome Home!&lt;/h3&gt;
    &lt;/div&gt;
  )
}

const postsLayoutRoute = createRoute({
  getParentRoute: () =&gt; rootRoute,
  path: &#39;posts&#39;,
  loader: ({ context: { queryClient } }) =&gt;
    queryClient.ensureQueryData(postsQueryOptions),
}).lazy(() =&gt; import(&#39;./posts.lazy&#39;).then((d) =&gt; d.Route))

const postsIndexRoute = createRoute({
  getParentRoute: () =&gt; postsLayoutRoute,
  path: &#39;/&#39;,
  component: PostsIndexRouteComponent,
})

function PostsIndexRouteComponent() {
  return &lt;div&gt;Select a post.&lt;/div&gt;
}

const postRoute = createRoute({
  getParentRoute: () =&gt; postsLayoutRoute,
  path: &#39;$postId&#39;,
  errorComponent: PostErrorComponent,
  loader: ({ context: { queryClient }, params: { postId } }) =&gt;
    queryClient.ensureQueryData(postQueryOptions(postId)),
  component: PostRouteComponent,
})

function PostErrorComponent({ error }: ErrorComponentProps) {
  const router = useRouter()
  if (error instanceof NotFoundError) {
    return &lt;div&gt;{error.message}&lt;/div&gt;
  }
  const queryErrorResetBoundary = useQueryErrorResetBoundary()

  React.useEffect(() =&gt; {
    queryErrorResetBoundary.reset()
  }, [queryErrorResetBoundary])

  return (
    &lt;div&gt;
      &lt;button
        onClick={() =&gt; {
          router.invalidate()
        }}
      &gt;
        retry
      &lt;/button&gt;
      &lt;ErrorComponent error={error} /&gt;
    &lt;/div&gt;
  )
}

function PostRouteComponent() {
  const { postId } = postRoute.useParams()
  const postQuery = useSuspenseQuery(postQueryOptions(postId))
  const post = postQuery.data

  return (
    &lt;div className=&quot;space-y-2&quot;&gt;
      &lt;h4 className=&quot;text-xl font-bold underline&quot;&gt;{post.title}&lt;/h4&gt;
      &lt;div className=&quot;text-sm&quot;&gt;{post.body}&lt;/div&gt;
    &lt;/div&gt;
  )
}

const pathlessLayoutRoute = createRoute({
  getParentRoute: () =&gt; rootRoute,
  id: &#39;_pathlessLayout&#39;,
  component: PathlessLayoutComponent,
})

function PathlessLayoutComponent() {
  return (
    &lt;div className=&quot;p-2&quot;&gt;
      &lt;div className=&quot;border-b&quot;&gt;I&#39;m a pathless layout&lt;/div&gt;
      &lt;div&gt;
        &lt;Outlet /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

const nestedPathlessLayoutRoute = createRoute({
  getParentRoute: () =&gt; pathlessLayoutRoute,
  id: &#39;_nestedPathlessLayout&#39;,
  component: Layout2Component,
})

function Layout2Component() {
  return (
    &lt;div&gt;
      &lt;div&gt;I&#39;m a nested pathless layout&lt;/div&gt;
      &lt;div className=&quot;flex gap-2 border-b&quot;&gt;
        &lt;Link
          to=&quot;/route-a&quot;
          activeProps={{
            className: &#39;font-bold&#39;,
          }}
        &gt;
          Go to route A
        &lt;/Link&gt;
        &lt;Link
          to=&quot;/route-b&quot;
          activeProps={{
            className: &#39;font-bold&#39;,
          }}
        &gt;
          Go to route B
        &lt;/Link&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;Outlet /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

const pathlessLayoutARoute = createRoute({
  getParentRoute: () =&gt; nestedPathlessLayoutRoute,
  path: &#39;/route-a&#39;,
  component: PathlessLayoutAComponent,
})

function PathlessLayoutAComponent() {
  return &lt;div&gt;I&#39;m layout A!&lt;/div&gt;
}

const pathlessLayoutBRoute = createRoute({
  getParentRoute: () =&gt; nestedPathlessLayoutRoute,
  path: &#39;/route-b&#39;,
  component: PathlessLayoutBComponent,
})

function PathlessLayoutBComponent() {
  return &lt;div&gt;I&#39;m layout B!&lt;/div&gt;
}

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: &#39;intent&#39;,
  // Since we&#39;re using React Query, we don&#39;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 &#39;@tanstack/react-router&#39; {
  interface Register {
    router: typeof router
  }
}

const rootElement = document.getElementById(&#39;app&#39;)!

if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement)

  root.render(
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;RouterProvider router={router} /&gt;
    &lt;/QueryClientProvider&gt;,
  )
}
">
<input type="hidden" name="project[files][src/posts.lazy.tsx]" value="import * as React from &#39;react&#39;
import { Link, Outlet, createLazyRoute } from &#39;@tanstack/react-router&#39;
import { useSuspenseQuery } from &#39;@tanstack/react-query&#39;
import { postsQueryOptions } from &#39;./posts&#39;

export const Route = createLazyRoute(&#39;/posts&#39;)({
  component: PostsLayoutComponent,
})

function PostsLayoutComponent() {
  const postsQuery = useSuspenseQuery(postsQueryOptions)

  const posts = postsQuery.data

  return (
    &lt;div className=&quot;p-2 flex gap-2&quot;&gt;
      &lt;ul className=&quot;list-disc pl-4&quot;&gt;
        {[...posts, { id: &#39;i-do-not-exist&#39;, title: &#39;Non-existent Post&#39; }].map(
          (post) =&gt; {
            return (
              &lt;li key={post.id} className=&quot;whitespace-nowrap&quot;&gt;
                &lt;Link
                  to=&quot;/posts/$postId&quot;
                  params={{
                    postId: post.id,
                  }}
                  className=&quot;block py-1 px-2 text-blue-600 hover:opacity-75&quot;
                  activeProps={{ className: &#39;font-bold underline&#39; }}
                &gt;
                  &lt;div&gt;{post.title.substring(0, 20)}&lt;/div&gt;
                &lt;/Link&gt;
              &lt;/li&gt;
            )
          },
        )}
      &lt;/ul&gt;
      &lt;Outlet /&gt;
    &lt;/div&gt;
  )
}
">
<input type="hidden" name="project[files][src/posts.ts]" value="import axios from &#39;redaxios&#39;
import { queryOptions } from &#39;@tanstack/react-query&#39;

export class NotFoundError extends Error {}

type PostType = {
  id: string
  title: string
  body: string
}

const fetchPosts = async () =&gt; {
  console.info(&#39;Fetching posts...&#39;)
  await new Promise((r) =&gt; setTimeout(r, 500))
  return axios
    .get&lt;Array&lt;PostType&gt;&gt;(&#39;https://jsonplaceholder.typicode.com/posts&#39;)
    .then((r) =&gt; r.data.slice(0, 10))
}

const fetchPost = async (postId: string) =&gt; {
  console.info(`Fetching post with id ${postId}...`)
  await new Promise((r) =&gt; setTimeout(r, 500))
  const post = await axios
    .get&lt;PostType&gt;(`https://jsonplaceholder.typicode.com/posts/${postId}`)
    .then((r) =&gt; r.data)

  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
  if (!post) {
    throw new NotFoundError(`Post with id &quot;${postId}&quot; not found!`)
  }

  return post
}

export const postQueryOptions = (postId: string) =&gt;
  queryOptions({
    queryKey: [&#39;posts&#39;, { postId }],
    queryFn: () =&gt; fetchPost(postId),
  })

export const postsQueryOptions = queryOptions({
  queryKey: [&#39;posts&#39;],
  queryFn: () =&gt; fetchPosts(),
})
">
<input type="hidden" name="project[files][src/styles.css]" value="@import &#39;tailwindcss&#39; source(&#39;../&#39;);

@layer base {
  *,
  ::after,
  ::before,
  ::backdrop,
  ::file-selector-button {
    border-color: var(--color-gray-200, currentcolor);
  }
}

html {
  color-scheme: light dark;
}
* {
  @apply border-gray-200 dark:border-gray-800;
}
body {
  @apply bg-gray-50 text-gray-950 dark:bg-gray-900 dark:text-gray-200;
}
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="tanstack-router-react-example-react-query">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>