Skip to content

Commit

Permalink
feat: add form and query example pages
Browse files Browse the repository at this point in the history
  • Loading branch information
sripwoud committed Feb 17, 2025
1 parent cddc2b6 commit 5413307
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 79 deletions.
6 changes: 5 additions & 1 deletion .biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@
"enabled": true,
"ignore": ["dist", "web/src/routeTree.gen.ts"],
"rules": {
"recommended": true,
"complexity": {
"useLiteralKeys": "off",
},
"correctness": {
// @tanstack/form form fields use children prop
"noChildrenProp": "off",
},
"recommended": true,
},
},
// organize imports with dprint
Expand Down
3 changes: 0 additions & 3 deletions web/src/components/About.tsx

This file was deleted.

12 changes: 12 additions & 0 deletions web/src/components/FieldInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { FieldApi } from '@tanstack/react-form'

// biome-ignore lint/suspicious/noExplicitAny: This is a generic component
export function FieldInfo({ field }: { field: FieldApi<any, any, any, any> }) {
return (
<>
{field.state.meta.isTouched && field.state.meta.errors.length
? <em style={{ color: 'red' }}>{field.state.meta.errors.join(', ')}</em>
: null}
</>
)
}
11 changes: 8 additions & 3 deletions web/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Link } from '@tanstack/react-router'
import { config } from 'l/config'
import { capitalize } from 'l/format'

const links = ['form', 'query', 'state'] as const

export function Header() {
return (
Expand All @@ -13,9 +16,11 @@ export function Header() {
<Link to='/'>
<h1 className='text-xl font-bold'>{config.appName}</h1>
</Link>
<Link to='/state'>State</Link>
<Link to='/users'>Users</Link>
<Link to='/about'>About</Link>
{links.map((r) => (
<Link key={r} to={`/${r}`}>
{capitalize(r)}
</Link>
))}
</nav>
)
}
3 changes: 3 additions & 0 deletions web/src/components/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function NotFound() {
return <h1>404 - Not Found</h1>
}
58 changes: 58 additions & 0 deletions web/src/components/UsernameForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useForm } from '@tanstack/react-form'
import { FieldInfo } from 'c/FieldInfo'
import { capitalize } from 'l/format'
import { type UsernameSchema, usernameSchema } from 'l/schemas'
import type { FormEvent } from 'react'

export function UsernameForm() {
const form = useForm<UsernameSchema>({
defaultValues: {
username: '',
} as UsernameSchema,
onSubmit: async ({ value: { username } }) => {
alert(`Submitted - Username: ${username}`)
},
validators: { onChange: usernameSchema },
})

function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}

return (
<form onSubmit={(e) => handleSubmit(e)}>
<form.Field
name='username'
children={(field) => (
<>
<label htmlFor={field.name} style={{ marginRight: '4px' }}>
{capitalize(field.name)}
</label>
<input
onChange={(e) => field.handleChange(e.target.value)}
id={field.name}
name={field.name}
type='text'
placeholder={field.name}
value={field.state.value}
style={{ marginRight: '4px' }}
/>
<br />
<FieldInfo field={field} />
<br />
</>
)}
/>
<form.Subscribe
selector={({ canSubmit, isSubmitting }) => [canSubmit, isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type='submit' aria-busy={isSubmitting} disabled={!canSubmit}>
Submit
</button>
)}
/>
</form>
)
}
Empty file removed web/src/hooks/.gitkeep
Empty file.
3 changes: 3 additions & 0 deletions web/src/lib/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function capitalize(str: string) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
3 changes: 2 additions & 1 deletion web/src/lib/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createRouter } from '@tanstack/react-router'
import { NotFound as defaultNotFoundComponent } from 'components/NotFound'
import { routeTree } from 'routeTree.gen'

export const router = createRouter({ routeTree })
export const router = createRouter({ defaultNotFoundComponent, routeTree })
5 changes: 5 additions & 0 deletions web/src/lib/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { z } from 'zod'
import { usernameSchema as username } from './username'

export const usernameSchema = z.object({ username })
export type UsernameSchema = z.infer<typeof usernameSchema>
7 changes: 7 additions & 0 deletions web/src/lib/schemas/username.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from 'zod'

const USERNAME_REGEX = /^[a-zA-Z0-9_]{1,20}$/

export const usernameSchema = z.string().regex(USERNAME_REGEX, {
message: 'Invalid username (should be alphanumeric and between 1 and 20 characters, `_` is allowed)',
})
112 changes: 56 additions & 56 deletions web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,31 @@ import { createFileRoute } from '@tanstack/react-router'
// Import Routes

import { Route as rootRoute } from './routes/__root'
import { Route as UsersImport } from './routes/users'
import { Route as StateImport } from './routes/state'
import { Route as QueryImport } from './routes/query'

// Create Virtual Routes

const AboutLazyImport = createFileRoute('/about')()
const StateLazyImport = createFileRoute('/state')()
const FormLazyImport = createFileRoute('/form')()
const IndexLazyImport = createFileRoute('/')()

// Create/Update Routes

const AboutLazyRoute = AboutLazyImport.update({
id: '/about',
path: '/about',
const StateLazyRoute = StateLazyImport.update({
id: '/state',
path: '/state',
getParentRoute: () => rootRoute,
} as any).lazy(() => import('./routes/about.lazy').then((d) => d.Route))
} as any).lazy(() => import('./routes/state.lazy').then((d) => d.Route))

const UsersRoute = UsersImport.update({
id: '/users',
path: '/users',
const FormLazyRoute = FormLazyImport.update({
id: '/form',
path: '/form',
getParentRoute: () => rootRoute,
} as any)
} as any).lazy(() => import('./routes/form.lazy').then((d) => d.Route))

const StateRoute = StateImport.update({
id: '/state',
path: '/state',
const QueryRoute = QueryImport.update({
id: '/query',
path: '/query',
getParentRoute: () => rootRoute,
} as any)

Expand All @@ -58,25 +58,25 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexLazyImport
parentRoute: typeof rootRoute
}
'/state': {
id: '/state'
path: '/state'
fullPath: '/state'
preLoaderRoute: typeof StateImport
'/query': {
id: '/query'
path: '/query'
fullPath: '/query'
preLoaderRoute: typeof QueryImport
parentRoute: typeof rootRoute
}
'/users': {
id: '/users'
path: '/users'
fullPath: '/users'
preLoaderRoute: typeof UsersImport
'/form': {
id: '/form'
path: '/form'
fullPath: '/form'
preLoaderRoute: typeof FormLazyImport
parentRoute: typeof rootRoute
}
'/about': {
id: '/about'
path: '/about'
fullPath: '/about'
preLoaderRoute: typeof AboutLazyImport
'/state': {
id: '/state'
path: '/state'
fullPath: '/state'
preLoaderRoute: typeof StateLazyImport
parentRoute: typeof rootRoute
}
}
Expand All @@ -86,47 +86,47 @@ declare module '@tanstack/react-router' {

export interface FileRoutesByFullPath {
'/': typeof IndexLazyRoute
'/state': typeof StateRoute
'/users': typeof UsersRoute
'/about': typeof AboutLazyRoute
'/query': typeof QueryRoute
'/form': typeof FormLazyRoute
'/state': typeof StateLazyRoute
}

export interface FileRoutesByTo {
'/': typeof IndexLazyRoute
'/state': typeof StateRoute
'/users': typeof UsersRoute
'/about': typeof AboutLazyRoute
'/query': typeof QueryRoute
'/form': typeof FormLazyRoute
'/state': typeof StateLazyRoute
}

export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexLazyRoute
'/state': typeof StateRoute
'/users': typeof UsersRoute
'/about': typeof AboutLazyRoute
'/query': typeof QueryRoute
'/form': typeof FormLazyRoute
'/state': typeof StateLazyRoute
}

export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/state' | '/users' | '/about'
fullPaths: '/' | '/query' | '/form' | '/state'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/state' | '/users' | '/about'
id: '__root__' | '/' | '/state' | '/users' | '/about'
to: '/' | '/query' | '/form' | '/state'
id: '__root__' | '/' | '/query' | '/form' | '/state'
fileRoutesById: FileRoutesById
}

export interface RootRouteChildren {
IndexLazyRoute: typeof IndexLazyRoute
StateRoute: typeof StateRoute
UsersRoute: typeof UsersRoute
AboutLazyRoute: typeof AboutLazyRoute
QueryRoute: typeof QueryRoute
FormLazyRoute: typeof FormLazyRoute
StateLazyRoute: typeof StateLazyRoute
}

const rootRouteChildren: RootRouteChildren = {
IndexLazyRoute: IndexLazyRoute,
StateRoute: StateRoute,
UsersRoute: UsersRoute,
AboutLazyRoute: AboutLazyRoute,
QueryRoute: QueryRoute,
FormLazyRoute: FormLazyRoute,
StateLazyRoute: StateLazyRoute,
}

export const routeTree = rootRoute
Expand All @@ -140,22 +140,22 @@ export const routeTree = rootRoute
"filePath": "__root.tsx",
"children": [
"/",
"/state",
"/users",
"/about"
"/query",
"/form",
"/state"
]
},
"/": {
"filePath": "index.lazy.tsx"
},
"/state": {
"filePath": "state.tsx"
"/query": {
"filePath": "query.tsx"
},
"/users": {
"filePath": "users.tsx"
"/form": {
"filePath": "form.lazy.tsx"
},
"/about": {
"filePath": "about.lazy.tsx"
"/state": {
"filePath": "state.lazy.tsx"
}
}
}
Expand Down
8 changes: 0 additions & 8 deletions web/src/routes/about.lazy.tsx

This file was deleted.

4 changes: 4 additions & 0 deletions web/src/routes/form.lazy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createLazyFileRoute } from '@tanstack/react-router'
import { UsernameForm as component } from 'c/UsernameForm'

export const Route = createLazyFileRoute('/form')({ component })
2 changes: 1 addition & 1 deletion web/src/routes/users.tsx → web/src/routes/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { UserProps } from 'c/User'
import { query } from 'l/query'
import { BarLoader as pendingComponent } from 'react-spinners'

export const Route = createFileRoute('/users')({
export const Route = createFileRoute('/query')({
component,
loader: async () =>
query.fetchQuery({
Expand Down
6 changes: 6 additions & 0 deletions web/src/routes/state.lazy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createLazyFileRoute } from '@tanstack/react-router'
import { Latom as component } from 'c/Latom'

export const Route = createLazyFileRoute('/state')({
component,
})
6 changes: 0 additions & 6 deletions web/src/routes/state.tsx

This file was deleted.

0 comments on commit 5413307

Please sign in to comment.