Introducing ReUI:Open-source UI components and apps built with React, Next.js and Tailwind CSS
Browse ReUI

Dynamic Validation Vue Vee Validate


Hello i want to validate dynamic fields using vee validate, Fields having the same name.
For Example


<template>
<!--begin::Wrapper-->
<div class="row">
<div v-for="key in count" :key="key">
<div class="col-md-3" v-for="field in formSchema.fields" :key="field.name">
<div class="fv-row mb-10" >
<label class="form-label required" :for="field.name">{{ field.label }}</label>
<Field :as="field.as"
:id="field.id+`[${key}]`"
:name="field.name"
:class="field.class"
:key="key"
v-mask="field.mask ? field.mask : false"
/>
<ErrorMessage
:name="field.name"
class="fv-plugins-message-container invalid-feedback"
></ErrorMessage>
</div>
</div>
</div>
<div class="row">
<button
type="button" class="btn btn-lg btn-success me-3" @click="add" id="add_more_fields">
Add Location
</button>
</div>
</div>
<!--end::Wrapper-->
</template>


Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(

Replies (5)


This looks like a clean approach to dynamically generating fields with Vee Validate. However, handling validation for fields with the same name might get tricky since Vee Validate uses name as the unique identifier for validation. You could try appending an index or key to the field names dynamically to ensure they are unique, like field.name + '_' + key. This should help Vee Validate distinguish between different instances of the same field type and display the correct error messages.

Also, as someone who loves generating creative content, this reminds me of a tool I recently used, the God Name Generator. It dynamically creates unique names with different themes, and it struck me how it’s similar to the way you’re dynamically handling these form fields. Maybe consider something like that for inspiration on structuring dynamic content cleanly.



I have attached (step 2) and the (horizontal) vue page, is there way to validate all the dynamic fields inside the step2, I feel like Yup Object is ignoring dynamic fields



Hi Syed,

I think "Form Generator" page describes how you can validate dynamically rendered fields. See https://vee-validate.logaretm.com/v4/tutorials/dynamic-form-generator#prerequisites

Regards,
Lauris Stepanovs,
Keenthemes Support Team




<!--STEP 2 START -->



<template>
<!--begin::Wrapper-->
<div class="row">
<div v-for="key in count" :key="key">
<div class="col-md-3" v-for="field in formSchema.fields" :key="field.name+key">
<div class="fv-row mb-10" >
<label class="form-label required" :for="field.name+key">{{ field.label }}</label>
<Field :as="field.as"
:
:name="field.name+key"
:class="field.class"
:key="key"
v-mask="field.mask ? field.mask : false"
/>
<ErrorMessage
:name="field.name+key"
class="fv-plugins-message-container invalid-feedback"
></ErrorMessage>
</div>
</div>
</div>
<div class="row">
<button
type="button" class="btn btn-lg btn-success me-3" @click="add" >
Add Location
</button>
</div>
</div>
<!--end::Wrapper-->
</template>

<script lang="ts">
import { defineComponent,computed } from "vue";
import { Field, ErrorMessage } from "vee-validate";
import {mask} from "vue-the-mask"
import { useStore } from "vuex";
import { useRouter } from "vue-router";
import { Actions } from "@/store/enums/StoreEnums";
import { Mutations } from "@/store/enums/StoreEnums";
import * as Yup from "yup";

export default defineComponent({
name: "step-2",
components: {
Field,
ErrorMessage,
},
directives: {
mask: (el, binding) => {
if (!binding.value) {
return;
}
mask(el, binding);
}
},
setup () {

},
data: function () {
const count = 1;
const formSchema = {
fields: [
{
label: "Shipper Contact Person",
name: `shipper_contact_person`,
id: `shipper_contact_person`,
as: "input",
class: "form-control form-control-lg form-control-solid",

},
{
id: `shipper_phone_number`,
label: "Shipper Phone Number",
name: "shipper_phone_number",
class: "form-control form-control-lg form-control-solid",
as: "input",
mask:"####-#######",

},
{
id: `shipper_email`,
label: "Shipper Email Address",
name: `shipper_email`,
class: "form-control form-control-lg form-control-solid",
as: "input",

},
{
id: `shipper_city`,
label: "Shipper City",
name: `shipper_city`,
class: "form-control form-control-lg form-control-solid",
as: "input",

},
{
id: `shipper_address`,
label: "Shipper Address",
name: `shipper_address`,
class: "form-control form-control-lg form-control-solid",
as: "textarea",

},
{
id: `shipper_brand_name`,
label: "Brand Name",
name: `shipper_brand_name`,
class: "form-control form-control-lg form-control-solid",
as: "input",

},
{
id: `shipper_cnic`,
label: "CNIC#",
name: `shipper_cnic`,
class: "form-control form-control-lg form-control-solid",
as: "input",
mask : "#####-#######-#"
},
],
};
return {
count,
values: {},
formSchema,
}
},
methods: {
add: function(){
this.count++;
},
remove: function () {
this.count--;
},
submit: function(){
// for (var key of Object.keys(this.values)) {
// console.log(key + " -> " + this.values[key])
// }
}


}
});
</script>

<!--STEP 2 END -->




<!--HORIZONTAL VUE START -->

<template>
<!--begin::Card-->
<div class="separator my-2"></div>
<div class="mb-10 text-center">
<!--begin::Title-->
<h1 class="text-dark mb-3">Sign Up</h1>
<!--end::Title-->
<!--begin::Link-->
<div class="text-gray-400 fw-semobold fs-4">
Already have an account?

<router-link to="/sign-in" class="link-primary fw-bold">
Sign in here
</router-link>
</div>
<!--end::Link-->
</div>
<div class="card">
<!--begin::Card body-->
<div class="card-body">
<!--begin::Stepper-->
<div
class="stepper stepper-links d-flex flex-column"

ref="horizontalWizardRef"
>
<!--begin::Nav-->
<div class="stepper-nav py-5 mt-5">
<!--begin::Step 1-->
<div class="stepper-item current" data-kt-stepper-element="nav">
<h3 class="stepper-title">Profile Information
</div>
<!--end::Step 1-->

<!--begin::Step 2-->
<div class="stepper-item" data-kt-stepper-element="nav">
<h3 class="stepper-title">Shipping Information
</div>
<!--end::Step 2-->

<!--begin::Step 3-->
<div class="stepper-item" data-kt-stepper-element="nav">
<h3 class="stepper-title">Banking Information
</div>

<!--end::Step 3-->

<!--begin::Step 4-->
<div class="stepper-item" data-kt-stepper-element="nav">
<h3 class="stepper-title">Documents & Legal
</div>
<!--end::Step 4-->

<!--begin::Step 5-->
<div class="stepper-item" data-kt-stepper-element="nav">
<h3 class="stepper-title">Login Information
</div>
<!--end::Step 5-->
</div>
<!--end::Nav-->

<!--begin::Form-->
<form
class="mx-auto mw-600px w-100 pt-15 pb-10"
novalidate="novalidate"

@submit="handleStep"
>
<!--begin::Step 1-->
<div class="current" data-kt-stepper-element="content" >
<Step1></Step1>
</div>
<!--end::Step 1-->

<!--begin::Step 2-->
<div data-kt-stepper-element="content" >
<Step2></Step2>
</div>
<!--end::Step 2-->

<!--begin::Step 3-->
<div data-kt-stepper-element="content" >
<Step3></Step3>
</div>
<!--end::Step 3-->

<!--begin::Step 4-->
<div data-kt-stepper-element="content" >
<Step4></Step4>
</div>
<!--end::Step 4-->

<!--begin::Step 5-->
<div data-kt-stepper-element="content" >
<Step5></Step5>
</div>
<!--end::Step 5-->

<!--begin::Actions-->
<div class="d-flex flex-stack pt-15">
<!--begin::Wrapper-->
<div class="mr-2">
<button
type="button"
class="btn btn-lg btn-light-primary me-3"
data-kt-stepper-action="previous"
@click="previousStep"
>
<span class="svg-icon svg-icon-4 me-1">
<inline-svg src="media/icons/duotune/arrows/arr063.svg" />
</span>
Back
</button>
</div>
<!--end::Wrapper-->

<!--begin::Wrapper-->
<div>
<button
type="button"
class="btn btn-lg btn-primary me-3"
data-kt-stepper-action="submit"
v-if="currentStepIndex === totalSteps - 1"
@click="formSubmit()"
>
<span class="indicator-label">
Submit
<span class="svg-icon svg-icon-3 ms-2 me-0">
<inline-svg src="media/icons/duotune/arrows/arr064.svg" />
</span>
</span>
<span class="indicator-progress">
Please wait...
<span
class="spinner-border spinner-border-sm align-middle ms-2"
></span>
</span>
</button>

<button v-else type="submit" class="btn btn-lg btn-primary">
Continue
<span class="svg-icon svg-icon-4 ms-1 me-0">
<inline-svg src="media/icons/duotune/arrows/arr064.svg" />
</span>
</button>
</div>
<!--end::Wrapper-->
</div>
<!--end::Actions-->
</form>
<!--end::Form-->
</div>
<!--end::Stepper-->
</div>
<!--end::Card body-->
</div>
<!--end::Card-->
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, ref } from "vue";
import { StepperComponent } from "@/assets/ts/components";
import { useForm } from "vee-validate";
import Swal from "sweetalert2/dist/sweetalert2.min.js";
import * as Yup from "yup";
import Step1 from "@/components/wizard/steps/Step1.vue";
import Step2 from "@/components/wizard/steps/Step2.vue";
import Step3 from "@/components/wizard/steps/Step3.vue";
import Step4 from "@/components/wizard/steps/Step4.vue";
import Step5 from "@/components/wizard/steps/Step5.vue";


interface IStep1 {
contact_name: string;
contact_person: string;
phone_no : string;
phone_no2 : string;
address : string;
cnic : string
city : string
ntn_no : string
url : string
account_type : string
reference_type : string
sale_person : string
}

interface IStep2 {
shipper_contact_person : string;
shipper_phone_number : string;
shipper_email : string;
shipper_city : string;
shipper_address : string;
shipper_brand_name : string;
shipper_cnic : string;
}

interface IStep3 {
businessName: string;
businessDescriptor: string;
businessType: string;
businessDescription: string;
businessEmail: string;
}

interface IStep4 {
nameOnCard: string;
cardNumber: string;
cardExpiryMonth: string;
cardExpiryYear: string;
cardCvv: string;
saveCard: string;
}

interface CreateAccount extends IStep1, IStep2, IStep3, IStep4 {}

export default defineComponent({
name: "kt-horizontal-wizard",
components: {
Step1,
Step2,
Step3,
Step4,
Step5,
},
setup() {
const _stepperObj = ref<StepperComponent | null>(null);
const horizontalWizardRef = ref<HTMLElement | null>(null);
const currentStepIndex = ref(0);
const formData = ref<CreateAccount>({
contact_name: "Contact Name",
contact_person: "Contact Person",
phone_no: "Phone Number",
phone_no2: "Phone Number 2",
address: "Address",
city : "City",
cnic : "CNIC#",
ntn_no : "NTN #",
url : "URL",
account_type : "Account Type",
reference_type : "Reference No#",
sale_person : "Sale Person",

shipper_contact_person : "Shipper Contact Person",
shipper_phone_number : "Shipper Phone Number",
shipper_email : "Shipper Email",
shipper_city : "Shipper City",
shipper_address : "Shipper Address",
shipper_brand_name : "Shipper Brand Name",
shipper_cnic : "Shipper CNIC",

businessName: "Keenthemes Inc.",
businessDescriptor: "KEENTHEMES",
businessType: "1",
businessDescription: "",
businessEmail: "corp@support.com",
nameOnCard: "Max Doe",
cardNumber: "4111 1111 1111 1111",
cardExpiryMonth: "1",
cardExpiryYear: "2",
cardCvv: "123",
saveCard: "1",
});

onMounted(() => {
_stepperObj.value = StepperComponent.createInsance(
horizontalWizardRef.value as HTMLElement
);
});

const createAccountSchema = [
Yup.object({
contact_name: Yup.string().required(),
contact_person: Yup.string().required(),
phone_no: Yup.string().required(),
phone_no2: Yup.string().required(),
address: Yup.string().required(),
city: Yup.string().required(),
cnic: Yup.string().required().min(15),
ntn_no : Yup.string().required(),
url : Yup.string().required(),
account_type : Yup.string().required(),
reference_type : Yup.string().required(),
sale_person : Yup.string().required(),

}),
Yup.object({
shipper_contact_person : Yup.string().required(),
shipper_phone_number : Yup.string().required(),
shipper_email : Yup.string().required(),
shipper_city : Yup.string(),
shipper_address : Yup.string(),
shipper_brand_name : Yup.string() ,
shipper_cnic : Yup.string(),
}),
Yup.object({
businessName: Yup.string().required().label("Business Name"),
businessDescriptor: Yup.string()
.required()
.label("Shortened Descriptor"),
businessType: Yup.string().required().label("Corporation Type"),
businessEmail: Yup.string().required().label("Contact Email"),
}),
Yup.object({
nameOnCard: Yup.string().required().label("Name On Card"),
cardNumber: Yup.string().required().label("Card Number"),
cardExpiryMonth: Yup.string().required().label("Expiration Month"),
cardExpiryYear: Yup.string().required().label("Expiration Year"),
cardCvv: Yup.string().required().label("CVV"),
}),
];

const currentSchema = computed(() => {
return createAccountSchema[currentStepIndex.value];
});

const { resetForm, handleSubmit } = useForm<
IStep1 | IStep2 | IStep3 | IStep4
>({
validationSchema: currentSchema,
});

const totalSteps = computed(() => {
if (!_stepperObj.value) {
return;
}

return _stepperObj.value.totatStepsNumber;
});

const handleStep = handleSubmit((values) => {
// resetForm({
// values: {
// ...formData.value,
// },
// });

console.log(formData.value);
for (const item in values) {
// eslint-disable-next-line no-prototype-builtins
if (values.hasOwnProperty(item)) {
if (values[item]) {
formData.value[item] = values[item];
}
}
}

currentStepIndex.value++;

if (!_stepperObj.value) {
return;
}

_stepperObj.value.goNext();
});

const previousStep = () => {
if (!_stepperObj.value) {
return;
}

currentStepIndex.value--;

_stepperObj.value.goPrev();
};

const formSubmit = () => {
Swal.fire({
text: "All is cool! Now you submit this form",
icon: "success",
buttonsStyling: false,
confirmButtonText: "Ok, got it!",
customClass: {
confirmButton: "btn fw-semobold btn-light-primary",
},
}).then(() => {
window.location.reload();
});
};

return {
horizontalWizardRef,
previousStep,
handleStep,
formSubmit,
totalSteps,
currentStepIndex,
};
},
});
</script>

<!--HORIZONTAL VUE END-->



Hi Syed,

The name attribute value should be unique for each field.

You can use your key variable to make name value unique and then use the name for validation.

:name="`${field.name}-${key}`"


Regards,
Lauris Stepanovs,
Keenthemes Support Team


Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(
Text formatting options
Submit
Here's a how to add some HTML formatting to your comment:
  • <pre></pre> for JS codes block
  • <pre lang="html"></pre> for HTML code block
  • <pre lang="scss"></pre> for SCSS code block
  • <pre lang="php"></pre> for PHP code block
  • <code></code> for single line of code
  • <strong></strong> to make things bold
  • <em></em> to emphasize
  • <ul><li></li></ul>  to make list
  • <ol><li></li></ol>  to make ordered list
  • <h3></h3> to make headings
  • <a></a> for links
  • <img> to paste in an image
  • <blockquote></blockquote> to quote somebody
  • happy  :)
  • shocked  :|
  • sad  :(