import * as React from 'react'
import { createRoot } from 'react-dom/client'
import { useForm } from '@tanstack/react-form'
import {
QueryClient,
QueryClientProvider,
useMutation,
useQuery,
} from '@tanstack/react-query'
import type { AnyFieldApi } from '@tanstack/react-form'
function FieldInfo({ field }: { field: AnyFieldApi }) {
return (
<>
{field.state.meta.isTouched && field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(',')}</em>
) : null}
{field.state.meta.isValidating ? 'Validating...' : null}
</>
)
}
class DB {
private data: { firstName: string; lastName: string }
constructor() {
this.data = { firstName: 'FirstName', lastName: 'LastName' }
}
getData(): { firstName: string; lastName: string } {
return { ...this.data }
}
async saveUser(value: { firstName: string; lastName: string }) {
this.data = value
return value
}
}
// Dummy Database to emulate server-side actions
const db = new DB()
export default function App() {
const { data, isLoading, refetch } = useQuery({
queryKey: ['data'],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return db.getData()
},
})
const saveUserMutation = useMutation({
mutationFn: async (value: { firstName: string; lastName: string }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
db.saveUser(value)
},
})
const form = useForm({
defaultValues: {
firstName: data?.firstName ?? '',
lastName: data?.lastName ?? '',
},
onSubmit: async ({ formApi, value }) => {
// Do something with form data
await saveUserMutation.mutateAsync(value)
// Invalidating query to recheck fresh data
await refetch()
// Reset the form to start-over with a clean state
formApi.reset()
},
})
if (isLoading) return <p>Loading..</p>
return (
<div>
<h1>Simple Form Example</h1>
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}}
>
<div>
{/* A type-safe field component*/}
<form.Field
name="firstName"
validators={{
onChange: ({ value }) =>
!value
? 'A first name is required'
: value.length < 3
? 'First name must be at least 3 characters'
: undefined,
onChangeAsyncDebounceMs: 500,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return (
value.includes('error') && 'No "error" allowed in first name'
)
},
}}
children={(field) => {
// Avoid hasty abstractions. Render props are great!
return (
<>
<label htmlFor={field.name}>First Name:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)
}}
/>
</div>
<div>
<form.Field
name="lastName"
children={(field) => (
<>
<label htmlFor={field.name}>Last Name:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<>
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : 'Submit'}
</button>
<button type="reset" onClick={() => form.reset()}>
Reset
</button>
</>
)}
/>
</form>
</div>
)
}
const rootElement = document.getElementById('root')!
const queryClient = new QueryClient()
createRoot(rootElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
)
import * as React from 'react'
import { createRoot } from 'react-dom/client'
import { useForm } from '@tanstack/react-form'
import {
QueryClient,
QueryClientProvider,
useMutation,
useQuery,
} from '@tanstack/react-query'
import type { AnyFieldApi } from '@tanstack/react-form'
function FieldInfo({ field }: { field: AnyFieldApi }) {
return (
<>
{field.state.meta.isTouched && field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(',')}</em>
) : null}
{field.state.meta.isValidating ? 'Validating...' : null}
</>
)
}
class DB {
private data: { firstName: string; lastName: string }
constructor() {
this.data = { firstName: 'FirstName', lastName: 'LastName' }
}
getData(): { firstName: string; lastName: string } {
return { ...this.data }
}
async saveUser(value: { firstName: string; lastName: string }) {
this.data = value
return value
}
}
// Dummy Database to emulate server-side actions
const db = new DB()
export default function App() {
const { data, isLoading, refetch } = useQuery({
queryKey: ['data'],
queryFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return db.getData()
},
})
const saveUserMutation = useMutation({
mutationFn: async (value: { firstName: string; lastName: string }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
db.saveUser(value)
},
})
const form = useForm({
defaultValues: {
firstName: data?.firstName ?? '',
lastName: data?.lastName ?? '',
},
onSubmit: async ({ formApi, value }) => {
// Do something with form data
await saveUserMutation.mutateAsync(value)
// Invalidating query to recheck fresh data
await refetch()
// Reset the form to start-over with a clean state
formApi.reset()
},
})
if (isLoading) return <p>Loading..</p>
return (
<div>
<h1>Simple Form Example</h1>
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
form.handleSubmit()
}}
>
<div>
{/* A type-safe field component*/}
<form.Field
name="firstName"
validators={{
onChange: ({ value }) =>
!value
? 'A first name is required'
: value.length < 3
? 'First name must be at least 3 characters'
: undefined,
onChangeAsyncDebounceMs: 500,
onChangeAsync: async ({ value }) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return (
value.includes('error') && 'No "error" allowed in first name'
)
},
}}
children={(field) => {
// Avoid hasty abstractions. Render props are great!
return (
<>
<label htmlFor={field.name}>First Name:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)
}}
/>
</div>
<div>
<form.Field
name="lastName"
children={(field) => (
<>
<label htmlFor={field.name}>Last Name:</label>
<input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
<FieldInfo field={field} />
</>
)}
/>
</div>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<>
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : 'Submit'}
</button>
<button type="reset" onClick={() => form.reset()}>
Reset
</button>
</>
)}
/>
</form>
</div>
)
}
const rootElement = document.getElementById('root')!
const queryClient = new QueryClient()
createRoot(rootElement).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</React.StrictMode>,
)
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.