react-vite-demo

react and vite demo

git clone https://9o.is/git/react-vite-demo.git

Form.tsx

(2007B)


      1 import { createContext, ReactNode, useContext, useRef, useState } from "react"
      2 
      3 type FormProps<T> = ({
      4     children: ReactNode
      5     ['aria-label']?: string
      6 }) & ({
      7     onSubmit?: (data: FormDataMap) => void
      8     validator: undefined
      9 } | {
     10     onSubmit: (data: T) => void
     11     validator: (data: FormDataMap) => T
     12 })
     13 
     14 export type FormDataMap = {
     15     [key: string]: FormDataEntryValue | undefined
     16 }
     17 
     18 type FormContextProps = {
     19     formError?: string
     20 }
     21 
     22 const FormContext = createContext<FormContextProps | undefined>(undefined)
     23 
     24 export function Form<T>({ onSubmit, validator, children, ...props }: FormProps<T>) {
     25     const formRef = useRef<HTMLFormElement>(null)
     26     const [formError, setFormError] = useState<string | undefined>()
     27 
     28     const internalOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
     29         event.preventDefault()
     30 
     31         const formData = new FormData(formRef.current || undefined)
     32         const data: FormDataMap = {}
     33 
     34         for (const [k, v] of formData.entries()) {
     35             data[k] = v
     36         }
     37 
     38         if (validator) {
     39             try {
     40                 onSubmit(validator(data))
     41             } catch (error) {
     42                 setFormError((error as Error).message)
     43             }
     44             return 
     45         }
     46         
     47         onSubmit?.(data)
     48     }
     49 
     50     return (
     51         <FormContext.Provider value={{ formError }}>
     52             <form onSubmit={internalOnSubmit} ref={formRef} {...props}>
     53                 {children}
     54             </form>
     55         </FormContext.Provider>
     56     )
     57 }
     58 
     59 type FormErrorProps = {
     60     children: (error: string) => ReactNode
     61 }
     62 
     63 function FormError({ children }: FormErrorProps) {
     64     const { formError } = useFormContext()
     65 
     66     return (
     67         <div role="alert">
     68             {formError ? children(formError) : <>&nbsp;</>}
     69         </div>
     70     )
     71 }
     72 
     73 function useFormContext() {
     74     const formContext = useContext(FormContext)
     75     if (!formContext) throw new Error('useFormContext() must be used within a Form')
     76     return formContext
     77 }
     78 
     79 Form.Error = FormError