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
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ updates:
- '@react-router/dev'
- '@react-router/node'
- '@react-router/serve'
tanstack:
patterns:
- '@tanstack/react-router'
- '@tanstack/react-start'
- '@tanstack/router-cli'
remix:
patterns:
- '@remix-run/cloudflare'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:
run: |
pnpm --filter nextjs-example exec next typegen
pnpm --filter react-router-example exec react-router typegen
pnpm --filter tanstack-start-example run generate-routes
- name: Typecheck examples
# Astro supports TypeScript 5 only and the legacy Remix example will be removed.
env:
Expand All @@ -109,6 +110,7 @@ jobs:
examples/react-router \
examples/react-spa \
examples/shadcn-ui \
examples/tanstack-start \
--pretty false

epicstack:
Expand Down
3 changes: 2 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"!rollup.config.js",
"/packages/**/*.js",
"/packages/**/*.mjs",
"/guide/functions/"
"/guide/functions/",
"/examples/tanstack-start/src/routeTree.gen.ts"
],
"overrides": [
{
Expand Down
6 changes: 6 additions & 0 deletions examples/tanstack-start/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.output
.tanstack
dist
/playwright-report
/test-results
16 changes: 16 additions & 0 deletions examples/tanstack-start/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# TanStack Start Example

This example demonstrates how to combine Conform with TanStack Start server
functions:

- [Basic form with manual validation](./src/routes/login.tsx)
- [Async validation](./src/routes/signup.tsx) ([with an async schema](./src/routes/signup-async-schema.tsx))
- [Dynamic form with data persistence](./src/routes/todos.tsx)
- [File uploads](./src/routes/file-upload.tsx) with `FormData` and native `File` values

Each route passes Conform's `FormData` to a TanStack Start server function with
`useServerFn`. The server function performs trusted validation and the mutation,
then its Conform submission report is applied back to the form. Redirects are
handled by `useServerFn`.

Try it out on [StackBlitz](https://stackblitz.com/github/edmundhung/conform/tree/main/examples/tanstack-start).
43 changes: 43 additions & 0 deletions examples/tanstack-start/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "tanstack-start-example",
"private": true,
"type": "module",
"imports": {
"#/*": "./src/*"
},
"scripts": {
"build": "vite build",
"dev": "vite dev --port 3000",
"generate-routes": "tsr generate",
"preview": "vite preview",
"test": "playwright test",
"typecheck": "tsr generate && tsc"
},
"dependencies": {
"@conform-to/react": "1.21.1",
"@conform-to/zod": "1.21.1",
"@tanstack/react-router": "1.170.31",
"@tanstack/react-start": "1.168.48",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"zod": "^4.4.3"
},
"devDependencies": {
"@playwright/test": "1.62.1",
"@tanstack/router-cli": "1.167.32",
"@types/node": "^22",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.1.0",
"typescript": "^5.9.3",
"vite": "^8.2.1"
},
"dependenciesMeta": {
"@conform-to/react": {
"injected": true
},
"@conform-to/zod": {
"injected": true
}
}
}
32 changes: 32 additions & 0 deletions examples/tanstack-start/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: 'tests',
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI ? 'github' : undefined,
use: {
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: devices['Desktop Chrome'],
},
{
name: 'firefox',
use: devices['Desktop Firefox'],
},
{
name: 'webkit',
use: devices['Desktop Safari'],
},
],
webServer: {
command: 'pnpm run build && pnpm run preview --port 5681',
port: 5681,
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
177 changes: 177 additions & 0 deletions examples/tanstack-start/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
import { Route as FileUploadRouteImport } from './routes/file-upload'
import { Route as LoginRouteImport } from './routes/login'
import { Route as SignupRouteImport } from './routes/signup'
import { Route as SignupAsyncSchemaRouteImport } from './routes/signup-async-schema'
import { Route as TodosRouteImport } from './routes/todos'

const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
const FileUploadRoute = FileUploadRouteImport.update({
id: '/file-upload',
path: '/file-upload',
getParentRoute: () => rootRouteImport,
} as any)
const LoginRoute = LoginRouteImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const SignupRoute = SignupRouteImport.update({
id: '/signup',
path: '/signup',
getParentRoute: () => rootRouteImport,
} as any)
const SignupAsyncSchemaRoute = SignupAsyncSchemaRouteImport.update({
id: '/signup-async-schema',
path: '/signup-async-schema',
getParentRoute: () => rootRouteImport,
} as any)
const TodosRoute = TodosRouteImport.update({
id: '/todos',
path: '/todos',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/file-upload': typeof FileUploadRoute
'/login': typeof LoginRoute
'/signup': typeof SignupRoute
'/signup-async-schema': typeof SignupAsyncSchemaRoute
'/todos': typeof TodosRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/file-upload': typeof FileUploadRoute
'/login': typeof LoginRoute
'/signup': typeof SignupRoute
'/signup-async-schema': typeof SignupAsyncSchemaRoute
'/todos': typeof TodosRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/file-upload': typeof FileUploadRoute
'/login': typeof LoginRoute
'/signup': typeof SignupRoute
'/signup-async-schema': typeof SignupAsyncSchemaRoute
'/todos': typeof TodosRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/file-upload'
| '/login'
| '/signup'
| '/signup-async-schema'
| '/todos'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/file-upload'
| '/login'
| '/signup'
| '/signup-async-schema'
| '/todos'
id:
| '__root__'
| '/'
| '/file-upload'
| '/login'
| '/signup'
| '/signup-async-schema'
| '/todos'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
FileUploadRoute: typeof FileUploadRoute
LoginRoute: typeof LoginRoute
SignupRoute: typeof SignupRoute
SignupAsyncSchemaRoute: typeof SignupAsyncSchemaRoute
TodosRoute: typeof TodosRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
'/file-upload': {
id: '/file-upload'
path: '/file-upload'
fullPath: '/file-upload'
preLoaderRoute: typeof FileUploadRouteImport
parentRoute: typeof rootRouteImport
}
'/login': {
id: '/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRouteImport
}
'/signup': {
id: '/signup'
path: '/signup'
fullPath: '/signup'
preLoaderRoute: typeof SignupRouteImport
parentRoute: typeof rootRouteImport
}
'/signup-async-schema': {
id: '/signup-async-schema'
path: '/signup-async-schema'
fullPath: '/signup-async-schema'
preLoaderRoute: typeof SignupAsyncSchemaRouteImport
parentRoute: typeof rootRouteImport
}
'/todos': {
id: '/todos'
path: '/todos'
fullPath: '/todos'
preLoaderRoute: typeof TodosRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
FileUploadRoute: FileUploadRoute,
LoginRoute: LoginRoute,
SignupRoute: SignupRoute,
SignupAsyncSchemaRoute: SignupAsyncSchemaRoute,
TodosRoute: TodosRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
17 changes: 17 additions & 0 deletions examples/tanstack-start/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createRouter as createTanStackRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';

export function getRouter() {
return createTanStackRouter({
routeTree,
scrollRestoration: true,
defaultPreload: 'intent',
defaultPreloadStaleTime: 0,
});
}

declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>;
}
}
Loading
Loading