Submit button
Submits the form, and shows a spinner when that takes a while.
Installation
npx shadcn@latest add @formdef/submit-buttonRegister it as a form component in lib/formdef/index.tsx, as formComponents: { SubmitButton },
and place it with a layout entry.
Usage
declare const : (: string) => Promise<void>
export const = createForm({
: .({ : .('Enter an email address') }),
: { : '' },
fields: { email: { : 'text', : { : 'Email' } } },
: ['email', { : 'SubmitButton', : { : 'Subscribe' } }],
onSubmit: ({ }) => (.),
})The button reads the form from the contexts in lib/formdef/context.ts, so it takes no form or
field prop.
- It is never disabled because the form is invalid. A click on an invalid form submits, and the errors appear at their fields.
- While a submit runs it is
aria-disabledandaria-busy, and a second click does nothing. - Once a submit has run for 300 ms, a spinner replaces its
icon. A fast submit shows none.
variant, size and className go to shadcn's Button.
Props
Source
'use client'
import { useStore } from '@tanstack/react-form'
import { LoaderCircleIcon } from 'lucide-react'
import type { ComponentProps, ReactNode } from 'react'
import { Button } from '@/components/ui/button'
import { useSlow } from '@/hooks/formdef/use-slow'
import { useFormContext } from '@/lib/formdef/context'
type SubmitButtonProps = Pick<ComponentProps<typeof Button>, 'variant' | 'size' | 'className'> & {
label: string
icon?: ReactNode
}
export const SubmitButton = ({ label, icon, ...attrs }: SubmitButtonProps) => {
const form = useFormContext()
const isSubmitting = useStore(form.store, (state) => state.isSubmitting)
const isSlow = useSlow(isSubmitting)
return (
<Button
{...attrs}
type="submit"
aria-busy={isSubmitting}
aria-disabled={isSubmitting}
onMouseDown={(event) => event.preventDefault()}
onClick={(event) => {
if (isSubmitting) {
event.preventDefault()
}
}}
>
{isSlow ? (
<LoaderCircleIcon
data-slot="spinner"
className="motion-safe:animate-spin motion-reduce:animate-pulse"
/>
) : (
icon
)}
{label}
</Button>
)
}