Búsqueda de sitios web

reactjs - Permitir cadena vacía como defaultValue pero rechazar en la validación con Zod, react-hook-form y Material UI


Estoy utilizando componentes de Material UI con react-hook-form y validación con zod. Tengo un campo de selección bloodType:

const bloodTypes = [ "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-" ] as const;
const schema = z.object({
  bloodType: z.enum(bloodTypes),
});

type FormType = z.infer<typeof schema>;

El problema viene en el componente mui en la propiedad defaultValue. Dado que este es un componente controlado, necesito darle un defaultValue pero Typescript se queja de que el valor "" no es asignable al tipo bloodType.

<FormControl fullWidth error={!!errors.bloodType} margin="normal">
  <InputLabel id="bloodtype-label">Tipo de sangre</InputLabel>
  <Controller
    name="bloodType"
    defaultValue=""  // <-------- HERE
    control={control}
    render={({ field }) => (
      <Select
        {...field}
        labelId="bloodtype-label"
        label="Tipo de sangre"
      >
        {bloodTypes.map((bloodtype) => (
          <MenuItem key={bloodtype} value={bloodtype}>
            {bloodtype}
          </MenuItem>
        ))}
      </Select>
    )}
  />
  <FormHelperText>{errors.bloodType?.message}</FormHelperText>
</FormControl>

Eso tiene sentido ya que bloodType solo permite los valores definidos en el esquema, pero me gustaría que el valor inicial estuviera vacío.

Intenté establecer el defaultValue en undefined pero entonces MUI lanza una alerta MUI: You have provided an out-of-range value 'undefined' for the select. Entonces mi pregunta es ¿cómo puedo permitir una cadena vacía como defaultValue pero rechazarla en la validación de zod y mantener los tipos correctos?

Como solución temporal, estoy usando .refine para verificar si el valor es "" y devolver falso para que falle la validación, pero siento que no es el enfoque correcto.

Respuesta #1

La forma que he encontrado para resolver esto es convirtiendo la cadena vacía al tipo de campo.

<Controller
    name="bloodType"
    defaultValue={"" as FormType["bloodType"]}  // <-------- HERE
    control={control}
    render={({ field }) => (
      <Select
        {...field}
        labelId="bloodtype-label"
        label="Tipo de sangre"
      >
        {bloodTypes.map((bloodtype) => (
          <MenuItem key={bloodtype} value={bloodtype}>
            {bloodtype}
          </MenuItem>
        ))}
      </Select>
    )}
  />

Esto también funciona dentro del hook useForm, si configuras tus defaultValues allí también.

const formResults = useForm<FormType>({
    resolver: zodResolver(schema),
    defaultValues: {
      bloodType: '' as FormType['bloodType'],
    },
  });

Creo que esto calma al compilador de TS de una manera que permite que el esquema se mantenga limpio sin declaraciones de refinamiento añadidas ni otra validación, y con la menor cantidad de 'mentiras' sobre los tipos y valores involucrados.

Respuesta #2

Sí, he estado enfrentando el mismo problema probablemente por siempre. Supongo que ahora mismo la única forma de superar esto es refinar O el método que creé, pero no es perfecto. Crea un tipo nullable en TypeScript
export type Nullable<T>={ [K in keyof T]: T[K] | null };
Luego úsalo en useForm así
useForm<Nullable<ShippingManage>>
Y ahora puedes proporcionar null en defaultValues, PERO se vuelve complicado cuando necesitas más manipulaciones y se vuelve algo complejo, así que es una opción para formularios más pequeños. Espero que encontremos algo mejor para resolver este problema...

Artículos relacionados