React hook form with zod submit error:
Im trying to implement custom components with react hook form with zod for validation
For Input Select RadioButton etc.
For now I implemented it for Form Wrapper aswell as Input component but for some reason when I submit I get following errors:
email: {message: 'Required', type: 'invalid_type', ref: undefined}
name: {message: 'Required', type: 'invalid_type', ref: undefined}
Any Ideas why that may be? Here is my implementation, Form wrapper:
import React, { ReactNode } from 'react'
import { FieldValues, SubmitErrorHandler, useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { ZodType } from 'zod'
interface HookForm {
children: ReactNode
onSubmit: (data: FieldValues) => void
onError: SubmitErrorHandler<FieldValues>
schema: ZodType<any, any, any>
}
export default function HookFormComponent<T extends FieldValues>({
children,
onSubmit,
onError,
schema,
}: HookForm) {
const { handleSubmit } = useForm<T>({
resolver: zodResolver(schema),
})
return <form onSubmit={handleSubmit(onSubmit, onError)}>{children}</form>
}
Input:
import { useForm } from 'react-hook-form'
interface Inputs extends Record<string, unknown> {
name: string
type?: string
placeholder?: string
className?: string
}
export default function HookFormInput(props: Inputs) {
const {
register,
formState: { errors },
} = useForm<Inputs>()
return (
<div>
<input
{...register(props.name)}
id={props.name}
type={props.type}
placeholder={props.placeholder}
className={props.className}
/>
{errors[props.name] && <span>{errors[props.name]?.message}</span>}
</div>
)
}
import React, { ReactNode } from 'react'
import { FieldValues, SubmitErrorHandler, useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { ZodType } from 'zod'
interface HookForm {
children: ReactNode
onSubmit: (data: FieldValues) => void
onError: SubmitErrorHandler<FieldValues>
schema: ZodType<any, any, any>
}
export default function HookFormComponent<T extends FieldValues>({
children,
onSubmit,
onError,
schema,
}: HookForm) {
const { handleSubmit } = useForm<T>({
resolver: zodResolver(schema),
})
return <form onSubmit={handleSubmit(onSubmit, onError)}>{children}</form>
}
Input:
import { useForm } from 'react-hook-form'
interface Inputs extends Record<string, unknown> {
name: string
type?: string
placeholder?: string
className?: string
}
export default function HookFormInput(props: Inputs) {
const {
register,
formState: { errors },
} = useForm<Inputs>()
return (
<div>
<input
{...register(props.name)}
id={props.name}
type={props.type}
placeholder={props.placeholder}
className={props.className}
/>
{errors[props.name] && <span>{errors[props.name]?.message}</span>}
</div>
)
}
1 Reply
and thats how Im trying to use it:
I found out what the issue was: the useForm from 2 different files holds a different instance, I fixed it and it works fine but idk if my implementation is that great, perhaps someone could tell me what I could improve in it or show me his/hers implementation for reacthookform + zod:
import HookFormComponent from 'modules/common/components/Form/HookForm'
import HookFormInput from 'modules/common/components/Form/HookFormInput'
import { z } from 'zod'
const WeAreEverything = () => {
const contactSchema = z.object({
name: z.string(),
email: z.string().email('needs to be an email'),
})
type ContactSchema = z.infer<typeof contactSchema>
return (
<div>
<HookFormComponent<ContactSchema>
onError={(err) => {
console.error(err, ': Form error values')
}}
onSubmit={(data) => {
console.log(data, ': Form values')
}}
schema={contactSchema}
>
<HookFormInput name="name" type="text" />
<HookFormInput name="email" type="email" />
<button type="submit">submit</button>
</HookFormComponent>
</div>
)
}
export default WeAreEverything
import HookFormComponent from 'modules/common/components/Form/HookForm'
import HookFormInput from 'modules/common/components/Form/HookFormInput'
import { z } from 'zod'
const WeAreEverything = () => {
const contactSchema = z.object({
name: z.string(),
email: z.string().email('needs to be an email'),
})
type ContactSchema = z.infer<typeof contactSchema>
return (
<div>
<HookFormComponent<ContactSchema>
onError={(err) => {
console.error(err, ': Form error values')
}}
onSubmit={(data) => {
console.log(data, ': Form values')
}}
schema={contactSchema}
>
<HookFormInput name="name" type="text" />
<HookFormInput name="email" type="email" />
<button type="submit">submit</button>
</HookFormComponent>
</div>
)
}
export default WeAreEverything
import { FieldErrors, FieldValues, UseFormRegister, FieldError, } from 'react-hook-form'
import React from 'react'
interface Inputs { name: string inputType: string register?: UseFormRegister<FieldValues>
errors?: FieldErrors<FieldValues> placeholder?: string className?: string
}
export default function HookFormInput(props: Inputs) {
return (
<div>
<input className={props.className} placeholder={props.placeholder}
type={props.inputType} {...props.register?.(props.name)}/>
{props.errors?.[props.name] && (
<span>{(props.errors[props.name] as FieldError)?.message}</span>
)}
</div>
)
}
Form wrapper:
import React, { ReactElement, ReactNode } from 'react'
import { DefaultValues, FieldValues, SubmitErrorHandler, useForm} from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { ZodType } from 'zod'
interface HookForm<T extends FieldValues> { children: ReactNode
onSubmit: (data: T) => void onError: SubmitErrorHandler<T> schema: ZodType<any, any, any>
defaultValues?: DefaultValues<T>}
export function HookFormComponent<T extends FieldValues>({ children, onSubmit, onError, schema, defaultValues, }: HookForm<T>) {
const { handleSubmit, register, formState: { errors }, } = useForm<T>({ defaultValues, resolver zodResolver(schema), })
const renderChildren = () => {
return React.Children.map(children as ReactElement, (child) => {
if (child.type === 'button') {
return React.cloneElement(child)
}
return React.cloneElement(child, {
register,
errors,
})
})
}
return (
<form onSubmit={handleSubmit(onSubmit, onError)}>{renderChildren()}</form>
)
}
import { FieldErrors, FieldValues, UseFormRegister, FieldError, } from 'react-hook-form'
import React from 'react'
interface Inputs { name: string inputType: string register?: UseFormRegister<FieldValues>
errors?: FieldErrors<FieldValues> placeholder?: string className?: string
}
export default function HookFormInput(props: Inputs) {
return (
<div>
<input className={props.className} placeholder={props.placeholder}
type={props.inputType} {...props.register?.(props.name)}/>
{props.errors?.[props.name] && (
<span>{(props.errors[props.name] as FieldError)?.message}</span>
)}
</div>
)
}
Form wrapper:
import React, { ReactElement, ReactNode } from 'react'
import { DefaultValues, FieldValues, SubmitErrorHandler, useForm} from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { ZodType } from 'zod'
interface HookForm<T extends FieldValues> { children: ReactNode
onSubmit: (data: T) => void onError: SubmitErrorHandler<T> schema: ZodType<any, any, any>
defaultValues?: DefaultValues<T>}
export function HookFormComponent<T extends FieldValues>({ children, onSubmit, onError, schema, defaultValues, }: HookForm<T>) {
const { handleSubmit, register, formState: { errors }, } = useForm<T>({ defaultValues, resolver zodResolver(schema), })
const renderChildren = () => {
return React.Children.map(children as ReactElement, (child) => {
if (child.type === 'button') {
return React.cloneElement(child)
}
return React.cloneElement(child, {
register,
errors,
})
})
}
return (
<form onSubmit={handleSubmit(onSubmit, onError)}>{renderChildren()}</form>
)
}