nextjs-demo

next.js demo using react 19 rc

git clone https://9o.is/git/nextjs-demo.git

Form.tsx

(2122B)


      1 import { createContext, ReactNode, useContext, useRef, useState } from "react"
      2 
      3 type FormProps<T> = ({
      4     children: ReactNode
      5     ['aria-label']?: string
      6     action?: (formData: FormData) => void
      7 }) & ({
      8     onSubmit?: (data: FormDataMap) => void
      9     validator: undefined
     10 } | {
     11     onSubmit?: (data: T) => void
     12     validator: (data: FormDataMap) => T
     13 })
     14 
     15 export type FormDataMap = {
     16     [key: string]: FormDataEntryValue | undefined
     17 }
     18 
     19 type FormContextProps = {
     20     formError?: string
     21 }
     22 
     23 const FormContext = createContext<FormContextProps | undefined>(undefined)
     24 
     25 export function Form<T>({ onSubmit, validator, children, action, ...props }: FormProps<T>) {
     26     const formRef = useRef<HTMLFormElement>(null)
     27     const [formError, setFormError] = useState<string | undefined>()
     28 
     29     const internalOnSubmit = (event: React.FormEvent<HTMLFormElement>) => {
     30         event.preventDefault()
     31 
     32         const formData = new FormData(formRef.current || undefined)
     33         const data: FormDataMap = Object.fromEntries(formData)
     34 
     35         if (validator) {
     36             try {
     37                 const validData = validator(data)
     38                 onSubmit?.(validData)
     39                 action?.(formData)
     40             } catch (error) {
     41                 setFormError((error as Error).message)
     42             }
     43             return 
     44         } else {
     45             onSubmit?.(data)
     46             action?.(formData)
     47         }
     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