Reputation: 121
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?
Upvotes: 8
Views: 27314
Reputation: 21
I had the same problem. I solved it by changing the controller name by referring to the register
rules documentation!
Before:
`test[${index}].email` // incorrect
After:
`test.${index}.email` // correct
Upvotes: 2
Reputation: 11
const { register, control, handleSubmit, formState: { errors } } = useForm({. resolver: yupResolver(schema), mode: 'onChange' })
errors[`users[${index}].email`]
Upvotes: 0
Reputation: 323
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);
},
});
Upvotes: 2
Reputation: 824
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>
Upvotes: 9
Reputation: 11
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
Upvotes: 1
Reputation: 19268
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>
);
};
Upvotes: 4