Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 94 95 96 97 98 99 100 101 102 103 104 105 | 1x 1x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 193x 177x 177x 146x 146x 31x 31x 31x 171x 177x 177x 177x 16x 193x 113x 217x 77x 77x 217x 25x 25x 115x 217x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x 113x | import { Children, cloneElement, FormHTMLAttributes, isValidElement, ReactNode, useState } from 'react'
import { FieldErrors, FieldValues, FormProvider, useForm, UseFormProps, UseFormReturn } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import { ObjectSchema, AnySchema } from 'yup'
import { BaseEntity } from '@/shared/@types/global'
import { useFormErrors } from '@/shared/hooks'
import { object } from 'yup'
import merge from 'lodash/merge'
type FormChildren<T extends FieldValues> =
| ReactNode
| ReactNode[]
| ((methods: UseFormReturn<T> & { isLoading: boolean }) => ReactNode)
export interface FormProps<T extends FieldValues = FieldValues>
extends Omit<FormHTMLAttributes<HTMLFormElement>, 'onSubmit' | 'onError' | 'children'> {
children?: FormChildren<T>
validationSchema?: ObjectSchema<Record<Exclude<keyof T, keyof BaseEntity>, AnySchema>, object>
formParams?: UseFormProps<T>
className?: string
onSubmit?: (data: T, methods?: UseFormReturn<T>) => Promise<unknown> | void
onError?: (errors?: FieldErrors<T>, methods?: UseFormReturn<T>) => Promise<unknown> | void
}
export const Form = <TFormValues extends FieldValues = FieldValues>({
children,
validationSchema,
formParams = {},
className = '',
onSubmit,
onError,
...rest
}: FormProps<TFormValues>) => {
const methods = useForm({
...formParams,
resolver: yupResolver(validationSchema || object({}).notRequired()),
})
const {
formState: { errors },
handleSubmit,
register,
} = methods
const { getErrorByName } = useFormErrors(errors)
const [isLoading, setIsLoading] = useState(false)
const onFormSubmit = async (data: TFormValues) => {
try {
setIsLoading(true)
await onSubmit?.(data, methods)
} catch (error) {
return Promise.reject(error)
} finally {
setIsLoading(false)
}
}
const normalizeChildren = (child: ReactNode) => {
if (isValidElement(child)) {
const name = child.props.name
if (!name) {
return cloneElement(child, child.props, normalize(child.props.children))
}
return cloneElement(child, {
...child.props,
...getErrorByName(name),
...(!child.props.control && { ...register(name) }),
key: name,
})
}
return child
}
const normalize = (childs?: ReactNode | ReactNode[]): ReactNode | ReactNode[] => {
if (!childs) {
return null
}
if (Array.isArray(childs)) {
return Children.map(childs, normalizeChildren)
}
return normalizeChildren(childs)
}
return (
<FormProvider {...methods}>
<form
onSubmit={handleSubmit(
data => onFormSubmit(data),
data => onError?.(data, methods)
)}
className={className}
{...rest}
noValidate
>
{normalize(typeof children === 'function' ? children(merge(methods, { isLoading })) : children)}
</form>
</FormProvider>
)
}
|