Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: unwrap initial value with useField.resetField fixes #3272 #3274

Merged
merged 2 commits into from Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/vee-validate/src/types.ts
Expand Up @@ -134,6 +134,7 @@ export interface FormContext<TValues extends Record<string, any> = Record<string
}>;
isSubmitting: Ref<boolean>;
handleSubmit(cb: SubmissionHandler<TValues>): (e?: Event) => Promise<void>;
setFieldInitialValue(path: string, value: unknown): void;
}

export interface PublicFormContext<TValues extends Record<string, any> = Record<string, any>>
Expand All @@ -147,6 +148,7 @@ export interface PublicFormContext<TValues extends Record<string, any> = Record<
| 'errorBag'
| 'setFieldErrorBag'
| 'stageInitialValue'
| 'setFieldInitialValue'
> {
errors: ComputedRef<FormErrors<TValues>>;
handleReset: () => void;
Expand Down
12 changes: 9 additions & 3 deletions packages/vee-validate/src/useField.ts
Expand Up @@ -335,9 +335,13 @@ function useValidationState<TValue>({
}) {
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<TValue>;

const initialValue = computed(() => {
return (getFromPath<TValue>(unref(formInitialValues), unref(name)) ?? unref(initValue)) as TValue;
return (getFromPath<TValue>(unref(formInitialValues), unref(name)) ?? unref(initialValueRef)) as TValue;
});

const value = useFieldValue(initialValue, name, form);
const meta = useMeta(initialValue, value, errors);

Expand Down Expand Up @@ -382,12 +386,14 @@ function useValidationState<TValue>({
const newValue =
state && 'value' in state
? (state.value as TValue)
: ((getFromPath<TValue>(unref(formInitialValues), fieldPath) ?? initValue) as TValue);
: ((getFromPath<TValue>(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 || []);
Expand Down Expand Up @@ -421,7 +427,7 @@ function useMeta<TValue>(initialValue: MaybeRef<TValue>, currentValue: Ref<TValu
validated: false,
initialValue: computed(() => unref(initialValue) as TValue | undefined),
dirty: computed(() => {
return !isEqual(currentValue.value, unref(initialValue));
return !isEqual(unref(currentValue), unref(initialValue));
}),
}) as FieldMeta<TValue>;

Expand Down
7 changes: 6 additions & 1 deletion packages/vee-validate/src/useForm.ts
Expand Up @@ -376,12 +376,16 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
};
};

function setFieldInitialValue(path: string, value: unknown) {
setInPath(initialValues.value, path, value);
}

/**
* Sneaky function to set initial field values
*/
function stageInitialValue(path: string, value: unknown) {
setInPath(formValues, path, value);
setInPath(initialValues.value, path, value);
setFieldInitialValue(path, value);
}

const schema = opts?.validationSchema;
Expand Down Expand Up @@ -412,6 +416,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
isSubmitting,
handleSubmit,
stageInitialValue,
setFieldInitialValue,
};

const immutableFormValues = computed<TValues>(() => {
Expand Down
4 changes: 2 additions & 2 deletions packages/vee-validate/tests/Field.spec.ts
Expand Up @@ -499,7 +499,7 @@ describe('<Field />', () => {
<span id="error">{{ errors && errors[0] }}</span>
<span id="touched">{{ meta.touched.toString() }}</span>
<span id="dirty">{{ meta.dirty.toString() }}</span>
<button @click="resetField({ value: '${resetValue}', dirty: true, touched: true, errors: ['${resetMessage}'] })">Reset</button>
<button @click="resetField({ value: '${resetValue}', touched: true, errors: ['${resetMessage}'] })">Reset</button>
</Field>
</div>
`,
Expand All @@ -521,7 +521,7 @@ describe('<Field />', () => {
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');
});

Expand Down
68 changes: 68 additions & 0 deletions packages/vee-validate/tests/useField.spec.ts
Expand Up @@ -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: `
<input name="field" v-model="value" />
<span id="meta">{{ meta.dirty ? 'dirty' : 'clean' }}</span>
<button @click="resetField()">Reset</button>
`,
});

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: `
<input name="field" v-model="value" />
<span id="meta">{{ meta.dirty ? 'dirty' : 'clean' }}</span>
<button @click="resetField({ value: '12' })">Reset</button>
`,
});

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');
});
});