'How to use yup validation on dynamic form using react-hook-form with useFieldArray

I'm trying to create a dynamic form using react-hook-form's useFieldArray hook. The user should be able to add or remove fields, thus making it dynamic. I've looked at this tutorial for inspiration, but the missing piece is how to implement error validation to the state, which will be an array of objects: {email: string}[]. (The object will take more key/value pairs. I've left out the rest for simplicity.)

I've tried using yup as validation schema. It looks like this:

const schema = yup.array().of(
  yup.object().shape({
    email: yup.string().email().required(),
  }),
)

The implementation into react-hook-form is:

import * as yup from 'yup'
import { yupResolver } from '@hookform/resolvers/yup'
import { useForm, useFieldArray, Controller } from 'react-hook-form'

const { register, control, handleSubmit, errors } = useForm({
  resolver: yupResolver(schema),
  mode: 'onChange',
})
const { fields, append, remove } = useFieldArray({
  control,
  name: 'users',
})

The form is more or less according to the tutorial in the link above.

When console logging the error object from useForm hook it is consistently giving an empty object {}. It doesn't seem like it works. I am probably missing something here. The question is what?



Solution 1:[1]

React Hook Form v7

yup with dynamic fields react hook form - useFieldArray

import { string, object, array } from 'yup';

const formSchema = {
  name: string().required("Name is required").min(7, 'Message'),
};

const schema = object({
  test: array()
   .of(object().shape(formSchema))
});

export default schema;
const methods = useForm({
  resolver: yupResolver(schema),
});

show the error

<div style={{ fontSize: 14 }}>{errors?.test?.[index]?.name?.message}</div>

Solution 2:[2]

perhaps you want to use the context argument to switch your schema?

Context: This context object is mutable and will be injected into resolver's second argument or Yup validation's context object.

import * as React from "react";
import { useForm } from "react-hook-form";
import * as Joi from "joi";

const validationSchema1 = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
});

const validationSchema2 = Joi.object({
  username: Joi.string()
    .alphanum()
    .min(3)
    .max(30)
    .required()
});

const App = () => {
  const [schemaContext, setSchemaContext] = useState({ schemaA: false })
  const { register, handleSubmit, errors } = useForm({
    context: schemaContext, // use the context switch here
    resolver: async (data, context) => {
      const { error, value: values } = context.is1 ? validationSchema1.validate(data, {
        abortEarly: false
      }) : validationSchema2.validate(data, {
        abortEarly: false
      });

      return {
        values: error ? {} : values,
        errors: error
          ? error.details.reduce((previous, currentError) => {
              return {
                ...previous,
                [currentError.path[0]]: currentError
              };
            }, {})
          : {}
      };
    }
  });

  const onSubmit = data => {
    console.log(data)
  };

  return (
    <div className="App">
      <h1>resolver</h1>
      
      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Username</label>
        <input type="text" name="username" ref={register} />
        {errors.username && <p>errors.username.message</p>}
        <input type="submit" />
      </form>
    </div>
  );
};

Solution 3:[3]

I think you should specify your array name. like this:

{
  teammates: yupArray().of(
      yupObject().shape({
        email: stringSchemaBuilder("Email").email(),
        expertise: arraySchemaBuilder(false, "Expertise", true, 2, 5),
      })
  ),
};

where teammates is my array name

Solution 4:[4]

I used the useForm hook's resolver property method to identify the validation scheme I should use from the value of a field in my form.

The code below worked for my use case maybe it can help you

function formValidatorSchemaByPaymentModalityType(paymentModalityType?: ModalityTypes) {
    switch (paymentModalityType) {
        case ModalityTypes.CREDIT_CARD:
          return creditCardSchemaValidation;
        default:
        return defaultSchemaValidation;
    }
}

const methods = useForm({
    resolver: (data, context, options) => {
      const validatorSchema = formValidatorSchemaByPaymentModalityType(
        data.paymentType.value,
      )
      return yupResolver(validatorSchema)(data, context, options);
    },
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Felipe Corredor
Solution 2 Bill
Solution 3 Haris Raharjo
Solution 4 Marcos Santos Dev