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> extends Omit, "onSubmit"> { /** All your form fields */ children?: ReactNode; /** Text to display in the submit button */ submitText?: string; schema?: S; onSubmit: (values: z.infer) => Promise; initialValues?: UseFormProps>["defaultValues"]; } interface OnSubmitResult { FORM_ERROR?: string; [prop: string]: any; } export const FORM_ERROR = "FORM_ERROR"; export function Form>({ children, submitText, schema, initialValues, onSubmit, ...props }: FormProps) { const ctx = useForm>({ mode: "onBlur", resolver: schema ? zodResolver(schema) : undefined, defaultValues: initialValues, }); const [formError, setFormError] = useState(null); return (
{ 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} > {/* Form fields supplied as children are rendered here */} {children} {formError && (
{formError}
)} {submitText && ( )}
); } export default Form;