Skip to content

Commit 31090e0

Browse files
committed
fix: double removal of path value FieldArray remove closes #4239
1 parent 9046308 commit 31090e0

File tree

6 files changed

+40
-10
lines changed

6 files changed

+40
-10
lines changed

.changeset/eleven-forks-search.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+
avoid double unset path with field array remove

packages/vee-validate/src/types/forms.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export interface PathState<TValue = unknown> {
8989
type: InputType;
9090
multiple: boolean;
9191
fieldsCount: number;
92+
__flags: {
93+
pendingUnmount: Record<string, boolean>;
94+
};
9295
validate?: FieldValidator;
9396
}
9497

@@ -248,6 +251,7 @@ export interface PrivateFormContext<TValues extends GenericObject = GenericObjec
248251
getAllPathStates(): PathState[];
249252
removePathState<TPath extends Path<TValues>>(path: TPath): void;
250253
unsetPathValue<TPath extends Path<TValues>>(path: TPath): void;
254+
markForUnmount(path: string): void;
251255
}
252256

253257
export interface BaseComponentBinds<TValue = unknown> {
@@ -258,7 +262,7 @@ export interface BaseComponentBinds<TValue = unknown> {
258262

259263
export type PublicPathState<TValue = unknown> = Omit<
260264
PathState<TValue>,
261-
'bails' | 'label' | 'multiple' | 'fieldsCount' | 'validate' | 'id' | 'type'
265+
'bails' | 'label' | 'multiple' | 'fieldsCount' | 'validate' | 'id' | 'type' | '__flags'
262266
>;
263267

264268
export interface ComponentBindsConfig<TValue = unknown, TExtraProps extends GenericObject = GenericObject> {
@@ -313,6 +317,7 @@ export interface FormContext<TValues extends GenericObject = GenericObject, TOut
313317
| 'setFieldInitialValue'
314318
| 'unsetInitialValue'
315319
| 'fieldArrays'
320+
| 'markForUnmount'
316321
| 'keepValuesOnUnmount'
317322
> {
318323
handleReset: () => void;

packages/vee-validate/src/useField.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ function _useField<TValue = unknown>(
115115
const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
116116
const form = (controlForm as PrivateFormContext | undefined) || injectedForm;
117117
const name = lazyToRef(path);
118-
let PENDING_UNMOUNT = false;
119118

120119
const validator = computed(() => {
121120
const schema = unref(form?.schema);
@@ -137,7 +136,7 @@ function _useField<TValue = unknown>(
137136
return normalizeRules(rulesValue);
138137
});
139138

140-
const { id, value, initialValue, meta, setState, errors } = useFieldState(name, {
139+
const { id, value, initialValue, meta, setState, errors, flags } = useFieldState(name, {
141140
modelValue,
142141
form,
143142
bails,
@@ -184,7 +183,7 @@ function _useField<TValue = unknown>(
184183
return validateCurrentValue('validated-only');
185184
},
186185
result => {
187-
if (PENDING_UNMOUNT) {
186+
if (flags.pendingUnmount[field.id]) {
188187
return;
189188
}
190189

@@ -405,15 +404,15 @@ function _useField<TValue = unknown>(
405404
});
406405

407406
onBeforeUnmount(() => {
408-
PENDING_UNMOUNT = true;
409407
const shouldKeepValue = unref(field.keepValueOnUnmount) ?? unref(form.keepValuesOnUnmount);
410408
const path = unravel(name);
411-
if (shouldKeepValue || !form) {
409+
if (shouldKeepValue || !form || flags.pendingUnmount[field.id]) {
412410
form?.removePathState(path);
413411

414412
return;
415413
}
416414

415+
flags.pendingUnmount[field.id] = true;
417416
const pathState = form.getPathState(path);
418417
const matchesId =
419418
Array.isArray(pathState?.id) && pathState?.multiple

packages/vee-validate/src/useFieldArray.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
120120
const newValue = [...pathValue];
121121
newValue.splice(idx, 1);
122122
const fieldPath = pathName + `[${idx}]`;
123+
form.markForUnmount(fieldPath);
123124
form.unsetInitialValue(fieldPath);
124125
setInPath(form.values, pathName, newValue);
125126
fields.value.splice(idx, 1);

packages/vee-validate/src/useFieldState.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { computed, isRef, reactive, ref, Ref, unref, watch } from 'vue';
2-
import { FieldMeta, FieldState, FieldValidator, InputType, MaybeRef, PrivateFormContext } from './types';
2+
import { FieldMeta, FieldState, FieldValidator, InputType, MaybeRef, PrivateFormContext, PathState } from './types';
33
import { getFromPath, isEqual, normalizeErrorItem } from './utils';
44

55
export interface StateSetterInit<TValue = unknown> extends FieldState<TValue> {
@@ -11,6 +11,7 @@ export interface FieldStateComposable<TValue = unknown> {
1111
path: MaybeRef<string>;
1212
meta: FieldMeta<TValue>;
1313
value: Ref<TValue>;
14+
flags: PathState['__flags'];
1415
initialValue: Ref<TValue>;
1516
errors: Ref<string[]>;
1617
setState(state: Partial<StateSetterInit<TValue>>): void;
@@ -62,6 +63,7 @@ export function useFieldState<TValue = unknown>(
6263
value,
6364
initialValue,
6465
meta,
66+
flags: { pendingUnmount: { [id]: false } },
6567
errors,
6668
setState,
6769
};
@@ -101,6 +103,7 @@ export function useFieldState<TValue = unknown>(
101103
errors,
102104
meta: state,
103105
initialValue,
106+
flags: state.__flags,
104107
setState,
105108
};
106109
}

packages/vee-validate/src/useForm.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,21 +241,24 @@ export function useForm<
241241
pathStateExists.multiple = true;
242242
}
243243

244+
const id = FIELD_ID_COUNTER++;
244245
if (Array.isArray(pathStateExists.id)) {
245-
pathStateExists.id.push(FIELD_ID_COUNTER++);
246+
pathStateExists.id.push(id);
246247
} else {
247-
pathStateExists.id = [pathStateExists.id, FIELD_ID_COUNTER++];
248+
pathStateExists.id = [pathStateExists.id, id];
248249
}
249250

250251
pathStateExists.fieldsCount++;
252+
pathStateExists.__flags.pendingUnmount[id] = false;
251253

252254
return pathStateExists as PathState<TValue>;
253255
}
254256

255257
const currentValue = computed(() => getFromPath(formValues, unravel(path)));
256258
const pathValue = unravel(path);
259+
const id = FIELD_ID_COUNTER++;
257260
const state = reactive({
258-
id: FIELD_ID_COUNTER++,
261+
id,
259262
path,
260263
touched: false,
261264
pending: false,
@@ -268,6 +271,9 @@ export function useForm<
268271
type: config?.type || 'default',
269272
value: currentValue,
270273
multiple: false,
274+
__flags: {
275+
pendingUnmount: { [id]: false },
276+
},
271277
fieldsCount: 1,
272278
validate: config?.validate,
273279
dirty: computed(() => {
@@ -467,6 +473,16 @@ export function useForm<
467473
}
468474
}
469475

476+
function markForUnmount(path: string) {
477+
return mutateAllPathState(s => {
478+
if (s.path.startsWith(path)) {
479+
keysOf(s.__flags.pendingUnmount).forEach(id => {
480+
s.__flags.pendingUnmount[id] = true;
481+
});
482+
}
483+
});
484+
}
485+
470486
const formCtx: PrivateFormContext<TValues, TOutput> = {
471487
formId,
472488
values: formValues,
@@ -501,6 +517,7 @@ export function useForm<
501517
removePathState,
502518
initialValues: initialValues as Ref<TValues>,
503519
getAllPathStates: () => pathStates.value,
520+
markForUnmount,
504521
};
505522

506523
/**

0 commit comments

Comments
 (0)