Skip to content

Commit 3d49faa

Browse files
committed
fix: ignore validation of removed array elements closes #3748
1 parent 67c2455 commit 3d49faa

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

packages/vee-validate/src/useField.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ function _useField<TValue = unknown>(
9696

9797
const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
9898

99+
// a flag indicating if the field is about to be removed/unmounted.
100+
let markedForRemoval = false;
99101
const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
100102
modelValue,
101103
standalone,
@@ -138,6 +140,11 @@ function _useField<TValue = unknown>(
138140
meta.pending = true;
139141
meta.validated = true;
140142
const result = await validateCurrentValue('validated-only');
143+
if (markedForRemoval) {
144+
result.valid = true;
145+
result.errors = [];
146+
}
147+
141148
setState({ errors: result.errors });
142149
meta.pending = false;
143150

@@ -146,6 +153,10 @@ function _useField<TValue = unknown>(
146153

147154
async function validateValidStateOnly(): Promise<ValidationResult> {
148155
const result = await validateCurrentValue('silent');
156+
if (markedForRemoval) {
157+
result.valid = true;
158+
}
159+
149160
meta.valid = result.valid;
150161

151162
return result;
@@ -290,6 +301,7 @@ function _useField<TValue = unknown>(
290301
form.register(field);
291302

292303
onBeforeUnmount(() => {
304+
markedForRemoval = true;
293305
form.unregister(field);
294306
});
295307

packages/vee-validate/tests/FieldArray.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { defineRule, useField } from '@/vee-validate';
2+
import { defineComponent } from '@vue/runtime-core';
3+
import { toRef } from 'vue';
14
import * as yup from 'yup';
25
import { mountWithHoc, setValue, getValue, dispatchEvent, flushPromises } from './helpers';
36

@@ -574,3 +577,92 @@ test('clears old errors path when item is removed when no form schema is present
574577

575578
expect(errorList.children).toHaveLength(2);
576579
});
580+
581+
// #3748
582+
test('clears old errors path when last item is removed and value update validation is on', async () => {
583+
const onSubmit = jest.fn();
584+
defineRule('required', (v: any) => (v ? true : REQUIRED_MESSAGE));
585+
const InputField = defineComponent({
586+
props: {
587+
rules: {
588+
type: String,
589+
required: true,
590+
},
591+
name: {
592+
type: String,
593+
required: true,
594+
},
595+
label: String,
596+
type: { type: String, default: 'text' },
597+
},
598+
setup(props) {
599+
const { value, handleChange, errors } = useField(toRef(props, 'name'), props.rules, {
600+
label: props.label,
601+
type: props.type,
602+
});
603+
604+
return {
605+
value,
606+
errors,
607+
handleChange,
608+
};
609+
},
610+
template: `
611+
<label :for="name">{{ label }}</label>
612+
<input :type="type" :name="name" :value="value" @input="handleChange" />
613+
<span>{{ errors[0] }}</span>
614+
`,
615+
});
616+
617+
mountWithHoc({
618+
components: {
619+
InputField,
620+
},
621+
setup() {
622+
const initialValues = {
623+
users: ['first', 'second', 'third'],
624+
};
625+
626+
const schema = yup.string().required();
627+
628+
return {
629+
onSubmit,
630+
schema,
631+
initialValues,
632+
};
633+
},
634+
template: `
635+
<VForm @submit="onSubmit" :initial-values="initialValues" v-slot="{ errors }">
636+
<FieldArray name="users" v-slot="{ remove, push, fields }">
637+
<fieldset v-for="(field, idx) in fields" :key="field.key">
638+
<legend>User #{{ idx }}</legend>
639+
<label :for="'name_' + idx">Name</label>
640+
<InputField :name="'users[' + idx + ']'" rules="required" />
641+
642+
<button class="remove" type="button" @click="remove(idx)">X</button>
643+
</fieldset>
644+
</FieldArray>
645+
646+
647+
<ul class="errors">
648+
<li v-for="error in errors">{{ error }}</li>
649+
</ul>
650+
651+
<button class="submit" type="submit">Submit</button>
652+
</VForm>
653+
`,
654+
});
655+
656+
await flushPromises();
657+
const submitBtn = document.querySelector('.submit') as HTMLButtonElement;
658+
const errorList = document.querySelector('ul') as HTMLUListElement;
659+
const removeBtnAt = (idx: number) => document.querySelectorAll('.remove')[idx] as HTMLButtonElement; // remove the second item
660+
661+
submitBtn.click();
662+
await flushPromises();
663+
expect(errorList.children).toHaveLength(0);
664+
removeBtnAt(2).click();
665+
await flushPromises();
666+
667+
expect(errorList.children).toHaveLength(0);
668+
});

0 commit comments

Comments
 (0)