diff --git a/packages/vee-validate/src/types.ts b/packages/vee-validate/src/types.ts index 73b41d3cb..549fde97f 100644 --- a/packages/vee-validate/src/types.ts +++ b/packages/vee-validate/src/types.ts @@ -134,6 +134,7 @@ export interface FormContext = Record; isSubmitting: Ref; handleSubmit(cb: SubmissionHandler): (e?: Event) => Promise; + setFieldInitialValue(path: string, value: unknown): void; } export interface PublicFormContext = Record> @@ -147,6 +148,7 @@ export interface PublicFormContext = Record< | 'errorBag' | 'setFieldErrorBag' | 'stageInitialValue' + | 'setFieldInitialValue' > { errors: ComputedRef>; handleReset: () => void; diff --git a/packages/vee-validate/src/useField.ts b/packages/vee-validate/src/useField.ts index 532ffe0ec..a44e9cbfc 100644 --- a/packages/vee-validate/src/useField.ts +++ b/packages/vee-validate/src/useField.ts @@ -335,9 +335,13 @@ function useValidationState({ }) { const { errors, errorMessage, setErrors } = useErrorsSource(name, form); const formInitialValues = injectWithSelf(FormInitialValuesSymbol, undefined); + // clones the ref value to a mutable version + const initialValueRef = ref(unref(initValue)) as Ref; + const initialValue = computed(() => { - return (getFromPath(unref(formInitialValues), unref(name)) ?? unref(initValue)) as TValue; + return (getFromPath(unref(formInitialValues), unref(name)) ?? unref(initialValueRef)) as TValue; }); + const value = useFieldValue(initialValue, name, form); const meta = useMeta(initialValue, value, errors); @@ -382,12 +386,14 @@ function useValidationState({ const newValue = state && 'value' in state ? (state.value as TValue) - : ((getFromPath(unref(formInitialValues), fieldPath) ?? initValue) as TValue); + : ((getFromPath(unref(formInitialValues), fieldPath) ?? unref(initValue)) as TValue); if (form) { form.setFieldValue(fieldPath, newValue, { force: true }); + form.setFieldInitialValue(fieldPath, newValue); } else { value.value = newValue; + initialValueRef.value = newValue; } setErrors(state?.errors || []); @@ -421,7 +427,7 @@ function useMeta(initialValue: MaybeRef, currentValue: Ref unref(initialValue) as TValue | undefined), dirty: computed(() => { - return !isEqual(currentValue.value, unref(initialValue)); + return !isEqual(unref(currentValue), unref(initialValue)); }), }) as FieldMeta; diff --git a/packages/vee-validate/src/useForm.ts b/packages/vee-validate/src/useForm.ts index da19e0c32..bb2a295f0 100644 --- a/packages/vee-validate/src/useForm.ts +++ b/packages/vee-validate/src/useForm.ts @@ -376,12 +376,16 @@ export function useForm = Record = Record(() => { diff --git a/packages/vee-validate/tests/Field.spec.ts b/packages/vee-validate/tests/Field.spec.ts index 9131d95fe..5bfec06b8 100644 --- a/packages/vee-validate/tests/Field.spec.ts +++ b/packages/vee-validate/tests/Field.spec.ts @@ -499,7 +499,7 @@ describe('', () => { {{ errors && errors[0] }} {{ meta.touched.toString() }} {{ meta.dirty.toString() }} - + `, @@ -521,7 +521,7 @@ describe('', () => { await flushPromises(); expect(error.textContent).toBe(resetMessage); expect(input.value).toBe(resetValue); - expect(dirty.textContent).toBe('true'); + expect(dirty.textContent).toBe('false'); expect(touched.textContent).toBe('true'); }); diff --git a/packages/vee-validate/tests/useField.spec.ts b/packages/vee-validate/tests/useField.spec.ts index f64517b6b..49295d631 100644 --- a/packages/vee-validate/tests/useField.spec.ts +++ b/packages/vee-validate/tests/useField.spec.ts @@ -95,4 +95,72 @@ describe('useField()', () => { await flushPromises(); expect(meta?.textContent).toBe('invalid'); }); + + test('dirty flag is false after reset', async () => { + mountWithHoc({ + setup() { + const { value, meta, resetField } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); + + return { + value, + meta, + resetField, + }; + }, + template: ` + + {{ meta.dirty ? 'dirty' : 'clean' }} + + `, + }); + + const input = document.querySelector('input') as HTMLInputElement; + const meta = document.querySelector('#meta'); + + await flushPromises(); + expect(meta?.textContent).toBe('clean'); + + setValue(input, ''); + await flushPromises(); + expect(meta?.textContent).toBe('dirty'); + + // trigger reset + document.querySelector('button')?.click(); + await flushPromises(); + expect(meta?.textContent).toBe('clean'); + }); + + test('dirty flag is false after reset with a new value', async () => { + mountWithHoc({ + setup() { + const { value, meta, resetField } = useField('field', val => (val ? true : REQUIRED_MESSAGE)); + + return { + value, + meta, + resetField, + }; + }, + template: ` + + {{ meta.dirty ? 'dirty' : 'clean' }} + + `, + }); + + const input = document.querySelector('input') as HTMLInputElement; + const meta = document.querySelector('#meta'); + + await flushPromises(); + expect(meta?.textContent).toBe('clean'); + + setValue(input, ''); + await flushPromises(); + expect(meta?.textContent).toBe('dirty'); + + // trigger reset + document.querySelector('button')?.click(); + await flushPromises(); + expect(meta?.textContent).toBe('clean'); + }); });