Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions integrations/react-start/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
.vinxi
.output
src/routeTree.gen.ts
25 changes: 25 additions & 0 deletions integrations/react-start/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "react-start-integration",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build"
},
"dependencies": {
"@tanstack/react-query": "workspace:*",
"@tanstack/react-router": "^1.166.1",
"@tanstack/react-router-ssr-query": "^1.166.1",
"@tanstack/react-start": "^1.166.1",
"react": "^19.2.1",
"react-dom": "^19.2.1"
},
"devDependencies": {
"@types/node": "^22.15.3",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "5.9.3",
"vite": "^6.4.1"
}
}
53 changes: 53 additions & 0 deletions integrations/react-start/src/components/default-catch-boundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
ErrorComponent,
Link,
rootRouteId,
useMatch,
useRouter,
} from '@tanstack/react-router'
import type { ErrorComponentProps } from '@tanstack/react-router'

export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
const router = useRouter()
const isRoot = useMatch({
strict: false,
select: (state) => state.id === rootRouteId,
})

console.error(error)

return (
<div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6">
<ErrorComponent error={error} />
<div className="flex gap-2 items-center flex-wrap">
<button
onClick={() => {
router.invalidate()
}}
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
>
Try Again
</button>
{isRoot ? (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
>
Home
</Link>
) : (
<Link
to="/"
className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`}
onClick={(e) => {
e.preventDefault()
window.history.back()
}}
>
Go Back
</Link>
)}
</div>
</div>
)
}
26 changes: 26 additions & 0 deletions integrations/react-start/src/components/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Link } from '@tanstack/react-router'
import type { ReactNode } from 'react'

export function NotFound({ children }: { children?: ReactNode }) {
return (
<div className="space-y-2 p-2">
<div className="text-gray-600 dark:text-gray-400">
{children || <p>The page you are looking for does not exist.</p>}
</div>
<p className="flex items-center gap-2 flex-wrap">
<button
onClick={() => window.history.back()}
className="bg-emerald-500 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
>
Go back
</button>
<Link
to="/"
className="bg-cyan-600 text-white px-2 py-1 rounded-sm uppercase font-black text-sm"
>
Start Over
</Link>
</p>
</div>
)
}
41 changes: 41 additions & 0 deletions integrations/react-start/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { DefaultCatchBoundary } from './components/default-catch-boundary'
import { NotFound } from './components/not-found'
import { routeTree } from './routeTree.gen'

export function getRouter() {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})

const router = createRouter({
routeTree,
context: { queryClient },
defaultPreload: 'intent',
defaultErrorComponent: DefaultCatchBoundary,
defaultNotFoundComponent: () => <NotFound />,
Wrap: ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
),
})

setupRouterSsrQueryIntegration({
queryClient,
router,
wrapQueryClient: false,
})

return router
}

declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>
}
}
40 changes: 40 additions & 0 deletions integrations/react-start/src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// <reference types="vite/client" />

import {
HeadContent,
Outlet,
Scripts,
createRootRouteWithContext,
} from '@tanstack/react-router'
import type { QueryClient } from '@tanstack/react-query'
import type { ReactNode } from 'react'

export const Route = createRootRouteWithContext<{
queryClient: QueryClient
}>()({
component: RootComponent,
})

function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
45 changes: 45 additions & 0 deletions integrations/react-start/src/routes/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { queryOptions, useSuspenseQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'
import * as React from 'react'

type Profile = {
isDefault: boolean
profileId: string
}

const profilesQueryOptions = queryOptions({
queryKey: ['profiles'],
queryFn: async (): Promise<Array<Profile>> => {
await new Promise((resolve) => setTimeout(resolve, 1000))

return [
{ profileId: 'default-profile', isDefault: true },
{ profileId: 'secondary-profile', isDefault: false },
]
},
retry: false,
})

export const Route = createFileRoute('/')({
component: Home,
})

function Home() {
return (
<main>
<h1>Profiles</h1>
<React.Suspense fallback={<p data-testid="loading">loading</p>}>
<ActiveProfile />
</React.Suspense>
</main>
)
}

function ActiveProfile() {
const profiles = useSuspenseQuery(profilesQueryOptions).data
const activeProfile = profiles.find((profile) => profile.isDefault)

return (
<p data-testid="active-profile">{activeProfile?.profileId ?? 'missing'}</p>
)
}
15 changes: 15 additions & 0 deletions integrations/react-start/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022",
"types": ["node", "vite/client"]
},
"include": ["src/**/*.ts", "src/**/*.tsx", "vite.config.ts"]
}
11 changes: 11 additions & 0 deletions integrations/react-start/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'vite'
import viteReact from '@vitejs/plugin-react'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'

export default defineConfig({
server: {
host: '127.0.0.1',
port: 3117,
},
plugins: [tanstackStart(), viteReact()],
})
1 change: 1 addition & 0 deletions packages/query-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
dehydrate,
hydrate,
} from './hydration'
export { pendingThenable } from './thenable'
export { InfiniteQueryObserver } from './infiniteQueryObserver'
export { MutationCache } from './mutationCache'
export type { MutationCacheNotifyEvent } from './mutationCache'
Expand Down
Loading
Loading