Skip to content

Commit

Permalink
fix: prevent yup schema from setting non-interacted fields errors closes
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Mar 22, 2021
1 parent 487ea34 commit 534f8b2
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 5 deletions.
1 change: 1 addition & 0 deletions packages/vee-validate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface FieldMeta<TValue> {
dirty: boolean;
valid: boolean;
pending: boolean;
hadValueUserInteraction: boolean;
initialValue?: TValue;
}

Expand Down
5 changes: 5 additions & 0 deletions packages/vee-validate/src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export function useField<TValue = unknown>(
}

value.value = newValue;
meta.hadValueUserInteraction = true;
if (!validateOnValueUpdate) {
return validateWithStateMutation();
}
Expand Down Expand Up @@ -364,6 +365,8 @@ function useValidationState<TValue>({
if (!hasCheckedAttr(type)) {
value.value = normalizeEventValue(e) as TValue;
}

meta.hadValueUserInteraction = true;
};

// Updates the validation state with the validation result
Expand All @@ -390,6 +393,7 @@ function useValidationState<TValue>({
setErrors(state?.errors || []);
meta.touched = state?.touched ?? false;
meta.pending = false;
meta.hadValueUserInteraction = false;
}

return {
Expand All @@ -413,6 +417,7 @@ function useMeta<TValue>(initialValue: MaybeReactive<TValue>, currentValue: Ref<
touched: false,
pending: false,
valid: true,
hadValueUserInteraction: false,
initialValue: computed(() => unref(initialValue) as TValue | undefined),
dirty: computed(() => {
return !isEqual(currentValue.value, unref(initialValue));
Expand Down
13 changes: 8 additions & 5 deletions packages/vee-validate/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,8 +538,10 @@ async function validateYupSchema<TValues>(
};

result[fieldId] = fieldResult;
const isTouched = Array.isArray(field) ? field.some(f => f.meta.touched) : field.meta.touched;
if (!shouldMutate && !isTouched) {
const hadInteraction = Array.isArray(field)
? field.some(f => f.meta.hadValueUserInteraction)
: field.meta.hadValueUserInteraction;
if (!shouldMutate && !hadInteraction) {
// Update the valid flag regardless to keep it accurate
if (Array.isArray(field)) {
field.forEach(f => (f.meta.valid = fieldResult.valid));
Expand Down Expand Up @@ -587,10 +589,11 @@ function useFormInitialValues<TValues extends Record<string, any>>(
return;
}

// update the pristine (non-touched fields)
// we exclude dirty and untouched fields because it's unlikely you want to change the form values using initial values
// update the non-interacted-by-user fields
// those are excluded because it's unlikely you want to change the form values using initial values
// we mostly watch them for API population or newly inserted fields
const isSafeToUpdate = (f: PrivateFieldComposite) => f.meta.touched;
// if the user API is taking too much time before user interaction they should consider disabling or hiding their inputs until the values are ready
const isSafeToUpdate = (f: PrivateFieldComposite) => f.meta.hadValueUserInteraction;
keysOf(fields.value).forEach(fieldPath => {
const field: PrivateFieldComposite | PrivateFieldComposite[] = fields.value[fieldPath];
const touchedByUser = Array.isArray(field) ? field.some(isSafeToUpdate) : isSafeToUpdate(field);
Expand Down
40 changes: 40 additions & 0 deletions packages/vee-validate/tests/Form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1659,4 +1659,44 @@ describe('<Form />', () => {
await flushPromises();
expect(span.textContent).toBe('valid');
});

// #3228
test('should not validate touched fields with yup schema if other fields value change', async () => {
const wrapper = mountWithHoc({
setup() {
const schema = yup.object({
email: yup.string().required(),
password: yup.string().required(),
});

return {
schema,
};
},
template: `
<VForm :validation-schema="schema" v-slot="{ errors }">
<Field id="email" name="email" as="input" :validate-on-blur="false" />
<Field id="password" name="password" as="input" :validate-on-blur="false" />
<span>{{ errors.email }}</span>
</VForm>
`,
});

await flushPromises();
const span = wrapper.$el.querySelector('span');
const email = wrapper.$el.querySelector('#email');
const password = wrapper.$el.querySelector('#password');
// the field is now blurred
dispatchEvent(email, 'blur');
await flushPromises();
// no error messages for email
expect(span.textContent).toBe('');

// should be valid now
setValue(password, '');
await flushPromises();
// again there should be no error messages for email, only the password
expect(span.textContent).toBe('');
});
});

0 comments on commit 534f8b2

Please sign in to comment.