6

I created a customer registration form, but all my "messages" from my "errors" are coming as required. Does anyone know what it could be? Something I set up wrong in zod or react-hook-form. Below I will leave the prints of the code.

This is my generic input component:

import { DetailedHTMLProps, InputHTMLAttributes } from 'react'

interface InputProps
  extends DetailedHTMLProps<
    InputHTMLAttributes<HTMLInputElement>,
    HTMLInputElement
  > {
  label: string
  error?: string
}

export const Input = ({ error, label, ...rest }: InputProps) => {
  return (
    <div className="flex flex-col">
      <label
        className="mb-2 block text-sm font-bold text-zinc-700"
        htmlFor={rest.id}
      >
        {label}
      </label>
      <input
        type="text"
        className={`appearance-none rounded-md border border-zinc-300 px-3 py-2 leading-tight shadow-sm focus:border-zinc-500 focus:outline-none
        ${error ? 'border-red-500' : 'border-zinc-300'}`}
        {...rest}
      />
      {error && <span className="text-xs text-red-500">{error}</span>}
    </div>
  )
}

And here is where I created my schema and where I am using this generic input component inside the form:

import { Input } from '../Input'
import { Modal } from '../Modal'

import { z } from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'

interface ClientRegistrationModalProps {
  isOpen: boolean
  onClose: () => void
}

const createUserFormSchema = z.object({
  name: z
    .string()
    .min(2, 'Password must be at least 2 characters long')
    .transform((name) =>
      name
        .trim()
        .split(' ')
        .map((word) => {
          return word[0].toLocaleUpperCase().concat(word.substring(1))
        })
        .join(' '),
    ),
  email: z
    .string()
    .email('Invalid e-mail format')
    .nonempty('E-mail is required'),
  birthdate: z.string().nonempty('Date of birth is mandatory'),
  cpf: z.string().length(11, 'CPF mus have 11 digits'),
})

type CreateUserFormData = z.infer<typeof createUserFormSchema>

export const ClientRegistrationModal = ({
  isOpen,
  onClose,
}: ClientRegistrationModalProps) => {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
  } = useForm<CreateUserFormData>({
    mode: 'all',
    resolver: zodResolver(createUserFormSchema),
  })

  // High-order function
  function createUser(data: CreateUserFormData) {
    console.log(data)
  }

  function resetForm() {
    onClose()
    reset()
  }

  console.log(errors)

  return (
    <Modal title="Cadastro de Cliente" isOpen={isOpen} onClose={resetForm}>
      <form onSubmit={handleSubmit(createUser)}>
        <div className="flex flex-col space-y-6">
          <Input
            label="Nome"
            type="text"
            id="name"
            placeholder="Enter your name"
            {...register('name')}
            error={errors.name?.message}
          />

          <Input
            label="Email"
            type="email"
            id="email"
            placeholder="Enter your e-mail"
            {...register('email')}
            error={errors.email?.message}
          />

          <Input
            label="Data de Nascimento"
            type="date"
            id="birthdate"
            {...register('birthdate')}
            error={errors.birthdate?.message}
          />

          <Input
            label="CPF"
            type="text"
            id="cpf"
            placeholder="Enter your CPF"
            {...register('cpf')}
            error={errors.cpf?.message}
          />
        </div>
        <div className="mt-4 flex items-center justify-between">
          <button
            className="mt-4 rounded bg-green-500 px-4 py-2 font-bold text-white transition-all ease-in hover:bg-green-700"
            type="submit"
          >
            Cadastrar
          </button>
          <button
            type="reset"
            className="mt-4 rounded bg-zinc-500 px-4 py-2 font-bold text-white transition-all ease-in hover:bg-zinc-700"
            onClick={resetForm}
            disabled={isSubmitting}
          >
            Cancelar
          </button>
        </div>
      </form>
    </Modal>
  )
}

The problem here is, when I give console.log(errors), my errors are all as "Required", that is, they are not showing the error messages that I set inside my schema.

I don't know what could be wrong and would like some help. Maybe it's some typing issue or something I configured wrong. I appreciate any help.

Instead of using that: name: z.string().min(2, 'The name needs at least 2 characters')

I used this: name: z.string().min(2,{ message: 'The name needs at least 2 characters' })

But it didn't work.

2 Answers 2

12

Solution

I was facing the same problem and it had to do with a bad setup between my custom Input component and the {...register("name")} props. Try changing your input to the standard <input /> component and see if the game changes... it probably will.

Honestly I have no idea why spreading the {...register("name")} is causing problems, but it is. Here's a fixed version of your component, all I did different was passing down the register function as a Prop and calling it inside the Input (Maybe you should rename the component to InputForm cus it's now a pretty specific component that only works with such function)

I also decided to pass down the name as a prop so we can properly call register. For convenience we can also infer the error messages with the name prop.

import { type DetailedHTMLProps, type InputHTMLAttributes } from "react";
import {
  type FieldErrors,
  type FieldValues,
  type Path,
  type UseFormRegister,
} from "react-hook-form";

interface InputProps<FormData extends FieldValues>
  extends DetailedHTMLProps<
    InputHTMLAttributes<HTMLInputElement>,
    HTMLInputElement
  > {
  label: string;
  name: Path<FormData>;
  register: UseFormRegister<FormData>;
  errors: FieldErrors<FormData>;
}

export const Input = <FormData extends FieldValues>({
  label,
  name,
  errors,
  register,
  ...rest
}: InputProps<FormData>) => {
  const error = errors?.[name]?.message as string | undefined;

  return (
    <div className="flex flex-col">
      <label
        className="mb-2 block text-sm font-bold text-zinc-700"
        htmlFor={rest.id}
      >
        {label}
      </label>
      <input
        type="text"
        className={`appearance-none rounded-md border border-zinc-300 px-3 py-2 leading-tight shadow-sm focus:border-zinc-500 focus:outline-none
        ${error ? "border-red-500" : "border-zinc-300"}`}
        {...rest}
        {...register(name)}
      />
      {error && <span className="text-xs text-red-500">{error}</span>}
    </div>
  );
};

Usage of the component

<Input
  label="Nome"
  type="text"
  placeholder="Enter your name"
  name="name"
  errors={errors}
  register={register}
/>

<Input
  label="Email"
  type="email"
  name="email"
  placeholder="Enter your e-mail"
  errors={errors}
  register={register}
/>
// ... and so forth

Here's the result: enter image description here

Workaround

If you still want to keep you component that way, there's a workaround, you'll need to specify your message in more than one place.

What's happening is that zod and react-hook-form generate different error messages depending on the situation, which is a great feature actually. So the error type that it is firing for you does not come from the message you've set in min() but from the z.string() part of the zod schema:

  name: z
    .string()
    .min(2, 'Password must be at least 2 characters long')

So to fix that, let's specify a custom message for the required_error in the string()

  name: z
    .string({ required_error: 'Passoword is required' })
    .min(2, 'Password must be at least 2 characters long')

Now you'll have a better error message when the form is submitted with an empty name field.

For anyone facing a similar problem but not exactly this one, I recommend logging the error message that is coming from react-hook-form so you can investigate where your error is coming from, that's how I figured how to fix this problem.

You can do that by passing a second parameter in the handleSubmit function and using that to log your error:


const onFormError:SubmitErrorHandler<CreateUserFormData> = (e) => {
   console.log(e)
}

// Pass down this function in the handleSubmit
<form onSubmit={handleSubmit(createUser, onFormError)}>
Sign up to request clarification or add additional context in comments.

Comments

0

I faced the same problem when using zod with react-hook-form. Even though i added custom message in zod object but still im getting error message as Required. after searching through the net i found this article.

In the article i found out this errors={errors["name"] && `${(errors["name"] as DeepMap<FieldValues, FieldError>).message}`} and it worked for me

<Input
  label="Nome"
  type="text"
  placeholder="Enter your name"
  name="name"
  errors={errors["name"] && `${(errors["name"] as DeepMap<FieldValues, FieldError>).message}`}
  register={register}
/>

DeepMap is a package from react-hook-form

reference: https://www.react-hook-form.com/ts/#FieldErrors

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.