Skip to content

Commit 9bfbfaa

Browse files
viajes7logaretm
authored andcommittedMay 11, 2023
feat: add isValidating state while validate method is running (#4244)
1 parent 09d5596 commit 9bfbfaa

File tree

14 files changed

+191
-2
lines changed

14 files changed

+191
-2
lines changed
 

‎.changeset/purple-eggs-ring.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vee-validate': patch
3+
---
4+
5+
feat: added isValidating to useForm

‎docs/src/pages/api/composition-helpers.mdx

+16
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,22 @@ useIsSubmitting.value; // true or false
258258

259259
<CodeTitle level="4">
260260

261+
`useIsValidating(): ComputedRef<boolean>`
262+
263+
</CodeTitle>
264+
265+
Returns a computed ref to the form's `isValidating` state.
266+
267+
```js
268+
import { useIsValidating } from 'vee-validate';
269+
270+
const isValidating = useIsValidating();
271+
272+
isValidating.value; // true or false
273+
```
274+
275+
<CodeTitle level="4">
276+
261277
`useSubmitCount(): ComputedRef<number>`
262278

263279
</CodeTitle>

‎docs/src/pages/api/form.mdx

+8
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ Indicates if the submission handler is still running, once it resolves/rejects i
167167

168168
<CodeTitle level="4">
169169

170+
`isValidating: boolean`
171+
172+
</CodeTitle>
173+
174+
Indicates if the validate function is still running, once validate function is done it will be automatically set to `false` again.
175+
176+
<CodeTitle level="4">
177+
170178
`meta: FormMeta`
171179

172180
</CodeTitle>

‎docs/src/pages/api/use-form.mdx

+15
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ type useForm = (opts?: FormOptions) => {
187187
errorBag: ComputedRef<Partial<Record<string, string[]>>>; // all error messages for each field
188188
meta: ComputedRef<FormMeta<TValues>>; // aggregate of the field's meta information
189189
isSubmitting: Ref<boolean>; // if the form submission function is being run
190+
isValidating: Ref<boolean>; // if the form validate function is being run
190191
setFieldValue<T extends keyof TValues>(field: T, value: TValues[T]): void; // Sets a field value
191192
setFieldError: (field: keyof TValues, message: string | string[] | undefined) => void; // Sets an error message for a field
192193
setErrors: (fields: FormErrors<TValues>) => void; // Sets error messages for fields
@@ -332,6 +333,20 @@ isSubmitting.value; // true or false
332333

333334
<CodeTitle level="4">
334335

336+
`isValidating: Ref<boolean>`
337+
338+
</CodeTitle>
339+
340+
Indicates if the validate function is still running, once validate function is done it will be automatically set to `false` again.
341+
342+
```js
343+
const { isValidating } = useForm();
344+
345+
isValidating.value; // true or false
346+
```
347+
348+
<CodeTitle level="4">
349+
335350
`meta: ComputedRef<FormMeta>`
336351

337352
</CodeTitle>

‎docs/src/pages/guide/composition-api/api-review.mdx

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Here is a list of the functions available that you can use:
6161
- `useResetForm` Resets the form to its initial state
6262
- `useSubmitForm` Creates a submission function that validates and submits the form (even if no `form` element is involved)
6363
- `useIsSubmitting` If the form is currently submitting
64+
- `useIsValidating` If the form is currently validating by validate function
6465
- `useSubmitCount` The number of times the user attempted to submit the form
6566
- `useFieldValue` Returns a specific fields' current value
6667
- `useFormValues` Returns the current form field values

‎packages/vee-validate/src/Form.ts

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type FormSlotProps = UnwrapRef<
1212
| 'errorBag'
1313
| 'values'
1414
| 'isSubmitting'
15+
| 'isValidating'
1516
| 'submitCount'
1617
| 'validate'
1718
| 'validateField'
@@ -86,6 +87,7 @@ const FormImpl = defineComponent({
8687
values,
8788
meta,
8889
isSubmitting,
90+
isValidating,
8991
submitCount,
9092
controlledValues,
9193
validate,
@@ -153,6 +155,7 @@ const FormImpl = defineComponent({
153155
errorBag: errorBag.value,
154156
values,
155157
isSubmitting: isSubmitting.value,
158+
isValidating: isValidating.value,
156159
submitCount: submitCount.value,
157160
controlledValues: controlledValues.value,
158161
validate,

‎packages/vee-validate/src/devtools.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ function buildFieldState(
430430
}
431431

432432
function buildFormState(form: PrivateFormContext): CustomInspectorState {
433-
const { errorBag, meta, values, isSubmitting, submitCount } = form;
433+
const { errorBag, meta, values, isSubmitting, isValidating, submitCount } = form;
434434

435435
return {
436436
'Form state': [
@@ -442,6 +442,10 @@ function buildFormState(form: PrivateFormContext): CustomInspectorState {
442442
key: 'isSubmitting',
443443
value: isSubmitting.value,
444444
},
445+
{
446+
key: 'isValidating',
447+
value: isValidating.value,
448+
},
445449
{
446450
key: 'touched',
447451
value: meta.value.touched,

‎packages/vee-validate/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export { useIsFieldDirty } from './useIsFieldDirty';
4141
export { useIsFieldTouched } from './useIsFieldTouched';
4242
export { useIsFieldValid } from './useIsFieldValid';
4343
export { useIsSubmitting } from './useIsSubmitting';
44+
export { useIsValidating } from './useIsValidating';
4445
export { useValidateField } from './useValidateField';
4546
export { useIsFormDirty } from './useIsFormDirty';
4647
export { useIsFormTouched } from './useIsFormTouched';

‎packages/vee-validate/src/types/devtools.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ export interface DevtoolsPluginFormState {
1313
errors: FormErrors<Record<string, any>>;
1414
values: Record<string, any>;
1515
isSubmitting: boolean;
16+
isValidating: boolean;
1617
submitCount: number;
1718
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export interface PrivateFormContext<TValues extends GenericObject = GenericObjec
231231
errors: ComputedRef<FormErrors<TValues>>;
232232
meta: ComputedRef<FormMeta<TValues>>;
233233
isSubmitting: Ref<boolean>;
234+
isValidating: Ref<boolean>;
234235
keepValuesOnUnmount: MaybeRef<boolean>;
235236
validateSchema?: (mode: SchemaValidationMode) => Promise<FormValidationResult<TValues, TOutput>>;
236237
validate(opts?: Partial<ValidationOptions>): Promise<FormValidationResult<TValues, TOutput>>;

‎packages/vee-validate/src/useForm.ts

+13
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ export function useForm<
119119
// If the form is currently submitting
120120
const isSubmitting = ref(false);
121121

122+
// If the form is currently validating
123+
const isValidating = ref(false);
124+
122125
// The number of times the user tried to submit the form
123126
const submitCount = ref(0);
124127

@@ -517,6 +520,7 @@ export function useForm<
517520
submitCount,
518521
meta,
519522
isSubmitting,
523+
isValidating,
520524
fieldArrays,
521525
keepValuesOnUnmount,
522526
validateSchema: unref(schema) ? validateSchema : undefined,
@@ -659,6 +663,8 @@ export function useForm<
659663
return formCtx.validateSchema(mode);
660664
}
661665

666+
isValidating.value = true;
667+
662668
// No schema, each field is responsible to validate itself
663669
const validations = await Promise.all(
664670
pathStates.value.map(state => {
@@ -680,6 +686,8 @@ export function useForm<
680686
})
681687
);
682688

689+
isValidating.value = false;
690+
683691
const results: Partial<FlattenAndSetPathsType<TValues, ValidationResult>> = {};
684692
const errors: Partial<FlattenAndSetPathsType<TValues, string>> = {};
685693
for (const validation of validations) {
@@ -748,6 +756,8 @@ export function useForm<
748756
return { valid: true, results: {}, errors: {} };
749757
}
750758

759+
isValidating.value = true;
760+
751761
const formResult =
752762
isYupValidator(schemaValue) || isTypedSchema(schemaValue)
753763
? await validateTypedSchema<TValues, TOutput>(schemaValue, formValues)
@@ -756,6 +766,8 @@ export function useForm<
756766
bailsMap: fieldBailsMap.value,
757767
});
758768

769+
isValidating.value = false;
770+
759771
return formResult;
760772
}
761773

@@ -805,6 +817,7 @@ export function useForm<
805817
...meta.value,
806818
values: formValues,
807819
isSubmitting: isSubmitting.value,
820+
isValidating: isValidating.value,
808821
submitCount: submitCount.value,
809822
}),
810823
refreshInspector,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { computed } from 'vue';
2+
import { FormContextKey } from './symbols';
3+
import { injectWithSelf, warn } from './utils';
4+
5+
/**
6+
* If the form is validating or not
7+
*/
8+
export function useIsValidating() {
9+
const form = injectWithSelf(FormContextKey);
10+
if (!form) {
11+
warn('No vee-validate <Form /> or `useForm` was detected in the component tree');
12+
}
13+
14+
return computed(() => {
15+
return form?.isValidating.value ?? false;
16+
});
17+
}

‎packages/vee-validate/tests/Form.spec.ts

+38-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineRule, useField, Form } from '@/vee-validate';
1+
import { defineRule, useField, Form, Field, useIsValidating } from '@/vee-validate';
22
import { mountWithHoc, setValue, setChecked, dispatchEvent, flushPromises } from './helpers';
33
import * as yup from 'yup';
44
import { computed, defineComponent, onErrorCaptured, reactive, ref, Ref } from 'vue';
@@ -1356,6 +1356,43 @@ describe('<Form />', () => {
13561356
expect(submitting.textContent).toBe('false');
13571357
});
13581358

1359+
test('isValidating state', async () => {
1360+
const spy = vi.fn((isValidating: boolean) => isValidating);
1361+
1362+
const Input = defineComponent({
1363+
components: {
1364+
Field,
1365+
},
1366+
template: `<Field name="field" />`,
1367+
setup() {
1368+
const isValidating = useIsValidating();
1369+
1370+
useField('field', () => {
1371+
spy(isValidating.value);
1372+
return true;
1373+
});
1374+
},
1375+
});
1376+
const wrapper = mountWithHoc({
1377+
components: {
1378+
Input,
1379+
},
1380+
template: `
1381+
<VForm v-slot="{ validate }">
1382+
<Input />
1383+
<button @click="validate">Validate</button>
1384+
</VForm>
1385+
`,
1386+
});
1387+
1388+
await flushPromises();
1389+
const button = wrapper.$el.querySelector('button');
1390+
button.click();
1391+
1392+
await flushPromises();
1393+
expect(spy).toHaveLastReturnedWith(true);
1394+
});
1395+
13591396
test('aggregated meta reactivity', async () => {
13601397
const wrapper = mountWithHoc({
13611398
template: `
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useForm, useIsValidating } from '@/vee-validate';
2+
import { mountWithHoc, flushPromises } from './helpers';
3+
import { expect } from 'vitest';
4+
import * as yup from 'yup';
5+
6+
describe('useIsValidating()', () => {
7+
test('indicates if a form is validating', async () => {
8+
const spy = vi.fn((isValidating: boolean) => isValidating);
9+
10+
mountWithHoc({
11+
setup() {
12+
const { validate } = useForm({
13+
validationSchema: {
14+
name: yup.string().test(() => {
15+
spy(isValidating.value);
16+
return true;
17+
}),
18+
},
19+
});
20+
21+
const isValidating = useIsValidating();
22+
23+
return {
24+
validate,
25+
};
26+
},
27+
template: `
28+
<button @click="validate">Submit</button>
29+
`,
30+
});
31+
32+
await flushPromises();
33+
// triggered by validateObjectSchema method
34+
expect(spy).toHaveBeenCalledTimes(1);
35+
const button = document.querySelector('button');
36+
button?.click();
37+
38+
await flushPromises();
39+
// triggered by formCtx validate method
40+
expect(spy).toHaveBeenCalledTimes(2);
41+
expect(spy).toHaveLastReturnedWith(true);
42+
});
43+
44+
test('returns false and warns if form is not found', async () => {
45+
const spy = vi.spyOn(console, 'warn').mockImplementation(() => {
46+
// NOOP
47+
});
48+
mountWithHoc({
49+
setup() {
50+
const isValidating = useIsValidating();
51+
52+
return {
53+
isValidating,
54+
};
55+
},
56+
template: `
57+
<span>{{ isValidating.toString() }}</span>
58+
`,
59+
});
60+
61+
await flushPromises();
62+
const validatingText = document.querySelector('span');
63+
expect(validatingText?.textContent).toBe('false');
64+
expect(console.warn).toHaveBeenCalled();
65+
spy.mockRestore();
66+
});
67+
});

0 commit comments

Comments
 (0)
Please sign in to comment.