Search code examples
react-hook-form

react-hook-form considers my number a string


I have a form where I am validating the types using zod:

const formSchema = z.object({
  numberField: z.number().min(1, "numberField is required"),
});

That looks like:

<form onSubmit={handleFormOnSubmit} className="space-y-4">   
  <div className="xs:col-span-full sm:col-span-3">
    <NumberField
      name="numberField"
      label="Insert a number"
      control={control}
    />
  </div>
</form>

The NumberField I use looks like this:

import React from "react";
import { useController } from "react-hook-form";
import type { FieldValues, UseControllerProps } from "react-hook-form";

import NumberInput from "../../Inputs/NumberInput";
import type { NumberInputProps } from "../../Inputs/NumberInput";

export type NumberFieldProps<P extends FieldValues> = UseControllerProps<P> &
  Omit<NumberInputProps, "name">;

const NumberField = <P extends FieldValues>({
  name,
  control,
  defaultValue,
  ...rest
}: NumberFieldProps<P>) => {
  const {
    field: { onChange, onBlur, value, name: fieldName },
    fieldState: { error },
  } = useController({ name, control, defaultValue });

  return (
    <NumberInput
      name={fieldName}
      onChange={onChange}
      onBlur={onBlur}
      value={value as number}
      error={error?.message}
      {...rest}
    />
  );
};

export default NumberField;

And my NumberInput like this:

import React from "react";
import clsx from "clsx";
import type { InputHTMLAttributes } from "react";

export interface NumberInputProps
  extends InputHTMLAttributes<HTMLInputElement> {
  name: string;
  label: string;
  error?: string;
  helperText?: string;
}

const NumberInput: React.FC<NumberInputProps> = ({
  label,
  error,
  helperText,
  className = "",
  name,
  required,
  value,
  ...inputProps
}) => {
  const inputClassName = clsx(
    "bg-gray-800 block border-0 flex-1 focus:ring-2 focus:ring-indigo-600 focus:ring-inset placeholder:text-gray-400 py-1.5 ring-1 ring-inset rounded-md shadow-sm sm:leading-6 sm:text-sm text-white w-full",
    {
      "ring-red-500": error,
      "ring-gray-700": !error,
    },
    className,
  );

  return (
    <div className="w-full">
      <label
        htmlFor={name}
        className="block text-sm font-medium leading-6 text-white"
      >
        {label} {required && <span className="text-red-500">*</span>}
      </label>
      <div className="mt-2">
        <input
          type="number"
          name={name}
          id={name}
          className={inputClassName}
          value={Number(value)}
          {...inputProps}
        />
      </div>
      {error && <p className="mt-1 text-sm text-red-500">{error}</p>}
      {helperText && <p className="mt-1 text-sm text-gray-400">{helperText}</p>}
    </div>
  );
};

export default NumberInput;

Somehow I am getting 'Expected number, received string' as a validation error, even if I can't put any string value in the form. Any advice of what I could be doing wrong or what I would need to do to fix it?

Many thanks in advance!


Solution

  • Setting type=number does not parse the output value. Neither value=Number(value). You have to parse it manually.

    I am not sure if it already has any provided methods to do that, like the one valueAsNumber, but you can always add that logic to the onChange of the controller

    // NumberInput
        <input 
           ...
           {...inputProps} 
           onChange={(event) => inputProps.onChange(Number(event.target.value))}
        />