forked from supabase/dbdev
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathForm.tsx
More file actions
93 lines (83 loc) · 2.5 KB
/
Form.tsx
File metadata and controls
93 lines (83 loc) · 2.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { zodResolver } from '@hookform/resolvers/zod'
import { AlertCircle } from 'lucide-react'
import {
createContext,
PropsWithChildren,
PropsWithoutRef,
useContext,
} from 'react'
import {
FieldValues,
FormProvider,
useForm,
UseFormReturn,
} from 'react-hook-form'
import { z } from 'zod'
import { Alert, AlertDescription } from '~/components/ui/alert'
import { cn } from '~/lib/utils'
// FORM_ERROR constant for backwards compatibility
export const FORM_ERROR = 'FINAL_FORM/form-error' as const
// Context to share form instance with child components
const FormContext = createContext<UseFormReturn<any> | null>(null)
export function useFormInstance<T extends FieldValues = FieldValues>() {
const form = useContext(FormContext)
if (!form) {
throw new Error('useFormInstance must be used within a Form')
}
return form as UseFormReturn<T>
}
export interface FormProps<S extends z.ZodType<any, any>> extends Omit<
PropsWithoutRef<JSX.IntrinsicElements['form']>,
'onSubmit'
> {
/** Zod schema for validation */
schema?: S
/** Called on form submission. Return { [FORM_ERROR]: message } to show error */
onSubmit: (
values: z.infer<S>
) => void | Promise<void | { [FORM_ERROR]?: string }>
/** Initial form values */
initialValues?: Partial<z.infer<S>>
}
function Form<S extends z.ZodType<any, any>>({
children,
schema,
initialValues,
onSubmit,
className,
...props
}: PropsWithChildren<FormProps<S>>) {
const form = useForm<z.infer<S>>({
resolver: schema ? zodResolver(schema) : undefined,
defaultValues: initialValues as any,
})
const handleSubmit = async (values: z.infer<S>) => {
const result = await onSubmit(values)
if (result && FORM_ERROR in result && result[FORM_ERROR]) {
form.setError('root', { message: result[FORM_ERROR] })
}
}
const rootError = form.formState.errors.root?.message
return (
<FormContext.Provider value={form}>
<FormProvider {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className={cn('flex flex-col gap-2', className)}
noValidate
{...props}
>
{rootError && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{rootError}</AlertDescription>
</Alert>
)}
{/* Form fields supplied as children are rendered here */}
{children}
</form>
</FormProvider>
</FormContext.Provider>
)
}
export default Form