import { useState , ReactNode , PropsWithoutRef } from "react" ; import { FormProvider , useForm , UseFormProps } from "react-hook-form" ; import { zodResolver } from "@hookform/resolvers/zod" ; import { z } from "zod" ; export interface FormProps < S extends z . ZodType < any , any > > extends Omit < PropsWithoutRef < JSX . IntrinsicElements [ "form" ] > , "onSubmit" > { /** All your form fields */ children ? : ReactNode ; /** Text to display in the submit button */ submitText ? : string ; schema ? : S ; onSubmit : ( values : z . infer < S > ) => Promise < void | OnSubmitResult > ; initialValues ? : UseFormProps < z . infer < S > > [ "defaultValues" ] ; } interface OnSubmitResult { FORM_ERROR ? : string ; [ prop ] : any ; } export const FORM_ERROR = "FORM_ERROR" ; export function Form < S extends z . ZodType < any , any > > ( { children , submitText , schema , initialValues , onSubmit , ... props } ) { const ctx = useForm < z . infer < S > > ( { mode : "onBlur" , resolver : schema ? zodResolver ( schema ) : undefined , defaultValues : initialValues , } ) ; const [ formError , setFormError ] = useState < string | null > ( null ) ; return ( < FormProvider { ... ctx } > < form onSubmit = { ctx . handleSubmit ( async ( values ) => { const result = ( await onSubmit ( values ) ) || { } ; for ( const [ key , value ] of Object . entries ( result ) ) { if ( key === FORM_ERROR ) { setFormError ( value ) ; } else { ctx . setError ( key as any , { type : "submit" , message : value , } ) ; } } } ) } className = "form" { ... props } > { } { children } { formError && ( < div role = "alert" style = { { color : "red" } } > { formError } < /div>)} { submitText && ( < button type = "submit" disabled = { ctx . formState . isSubmitting } > { submitText } < /button>)} < style global jsx > { `
          .form > * + * {
            margin-top: 1rem;
          }
        ` } < /style> < /form> < /FormProvider>); } export default Form ;