Skip to content

Commit fe322a0

Browse files
committed
fix: batch and sort paths for safer unsets closes #4115
1 parent 9b50cad commit fe322a0

File tree

4 files changed

+65
-3
lines changed

4 files changed

+65
-3
lines changed

.changeset/lucky-pears-kick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vee-validate': patch
3+
---
4+
5+
batch unsets and sort paths unset order for safer unsets closes #4115

packages/vee-validate/src/useField.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ComponentInternalInstance,
1111
onBeforeUnmount,
1212
warn,
13+
nextTick,
1314
} from 'vue';
1415
import { klona as deepCopy } from 'klona/full';
1516
import { validate as validateValue } from './validate';
@@ -434,7 +435,7 @@ function _useField<TValue = unknown>(
434435
pathState.id.splice(pathState.id.indexOf(field.id), 1);
435436
}
436437
} else {
437-
form.unsetPathValue(path);
438+
form.unsetPathValue(unravel(name));
438439
}
439440

440441
form.removePathState(path, id);

packages/vee-validate/src/useForm.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,23 @@ export function useForm<
381381
return pathState as PathState<PathValue<TValues, TPath>> | undefined;
382382
}
383383

384+
let UNSET_BATCH: Path<TValues>[] = [];
385+
let PENDING_UNSET: Promise<void> | null;
384386
function unsetPathValue<TPath extends Path<TValues>>(path: TPath) {
385-
unsetPath(formValues, path);
387+
UNSET_BATCH.push(path);
388+
if (!PENDING_UNSET) {
389+
PENDING_UNSET = nextTick(() => {
390+
const sortedPaths = [...UNSET_BATCH].sort().reverse();
391+
sortedPaths.forEach(p => {
392+
unsetPath(formValues, p);
393+
});
394+
395+
UNSET_BATCH = [];
396+
PENDING_UNSET = null;
397+
});
398+
}
399+
400+
return PENDING_UNSET;
386401
}
387402

388403
function makeSubmissionFactory(onlyControlled: boolean) {

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

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineRule, useField } from '@/vee-validate';
1+
import { Form, defineRule, useField } from '@/vee-validate';
22
import { toRef, ref, defineComponent } from 'vue';
33
import * as yup from 'yup';
44
import { mountWithHoc, setValue, getValue, dispatchEvent, flushPromises } from './helpers';
@@ -980,3 +980,44 @@ test('adding or removing fields should update form dirty correctly', async () =>
980980
await flushPromises();
981981
expect(dirty.textContent).toBe('false');
982982
});
983+
984+
// #4115
985+
test('removing fields with `v-if` should clean up their state properly', async () => {
986+
const showFields = ref(true);
987+
const formRef = ref<InstanceType<typeof Form>>();
988+
const initialValues = {
989+
users: [
990+
{ name: 'test 1', amount: 123 },
991+
{ name: 'test 2', amount: 567 },
992+
],
993+
};
994+
mountWithHoc({
995+
setup() {
996+
return {
997+
initialValues,
998+
formRef,
999+
showFields,
1000+
};
1001+
},
1002+
template: `
1003+
<VForm ref="formRef" :initial-values="initialValues">
1004+
<FieldArray name="users" v-slot="{ fields }">
1005+
<fieldset v-for="(field, idx) in fields" :key="field.key">
1006+
<legend>User #{{ idx }}</legend>
1007+
<template v-if="showFields">
1008+
<Field :name="'users[' + idx + '].name'" />
1009+
<Field :name="'users[' + idx + '].amount'" />
1010+
</template>
1011+
</fieldset>
1012+
</FieldArray>
1013+
1014+
</VForm>
1015+
`,
1016+
});
1017+
1018+
await flushPromises();
1019+
expect(formRef.value?.getValues()).toEqual(initialValues);
1020+
showFields.value = false;
1021+
await flushPromises();
1022+
expect(formRef.value?.getValues()).toEqual({});
1023+
});

0 commit comments

Comments
 (0)