diff --git a/docs/content/api/composition-helpers.md b/docs/content/api/composition-helpers.md index d46c5a497..e43b83987 100644 --- a/docs/content/api/composition-helpers.md +++ b/docs/content/api/composition-helpers.md @@ -98,7 +98,7 @@ import { useIsFormDirty } from 'vee-validate'; const isDirty = useIsFormDirty(); -isDirty.value; // if form exists: true or false +isDirty.value; // true or false ``` @@ -114,7 +114,7 @@ import { useIsFieldTouched } from 'vee-validate'; const isTouched = useIsFieldTouched('fieldName'); -isTouched.value; // if form exists: true or false +isTouched.value; // true or false ``` You can also use it in a child component that has a parent that used `useField`, The `useIsFieldTouched` will automatically pick up the field and produce its meta `touched` value @@ -139,7 +139,7 @@ import { useIsFormTouched } from 'vee-validate'; const isTouched = useIsFormTouched(); -isTouched.value; // if form exists: true or false +isTouched.value; // true or false ``` @@ -155,7 +155,7 @@ import { useIsFieldValid } from 'vee-validate'; const isValid = useIsFieldValid('fieldName'); -isValid.value; // if form exists: true or false +isValid.value; // true or false ``` You can also use it in a child component that has a parent that used `useField`, The `useIsFieldValid` will automatically pick up the field and produce its meta `valid` value @@ -186,7 +186,7 @@ import { useIsFormValid } from 'vee-validate'; const isValid = useIsFormValid(); -isValid.value; // if form exists: true or false +isValid.value; // true or false ``` @@ -283,3 +283,44 @@ const resetForm = useResetForm(); resetForm(); // resets the form ``` + + + +`useFieldValue(field?: string): ComputedRef` + + + +Returns a computed ref to the specified field's current value. + +```js +import { useFieldValue } from 'vee-validate'; + +const currentValue = useFieldValue('fieldName'); + +currentValue.value; +``` + +You can also use it in a child component that has a parent that used `useField`, The `useFieldValue` will automatically pick up the field and produce its current value. + +```js +import { useFieldValue } from 'vee-validate'; + +// Will look for the first parent that used `useField` +const currentValue = useFieldValue(); +``` + + + +`useFormValues(): ComputedRef>` + + + +Returns a computed ref to the context form current values. + +```js +import { useFormValues } from 'vee-validate'; + +const values = useFormValues(); + +values.value; +``` diff --git a/docs/content/guide/composition-api.md b/docs/content/guide/composition-api.md index 191143a11..3ab0d745c 100644 --- a/docs/content/guide/composition-api.md +++ b/docs/content/guide/composition-api.md @@ -192,5 +192,7 @@ Here is a list of the functions available that you can use: - `useResetForm` Resets the form to its initial state - `useIsSubmitting` If the form is currently submitting - `useSubmitCount` The number of times the user attempted to submit the form +- `useFieldValue` Returns a specific fields' current value +- `useFormValues` Returns the current form field values For more information about the functions, you can head over to the [API reference and check them out](/api/composition-helpers). diff --git a/packages/vee-validate/src/index.ts b/packages/vee-validate/src/index.ts index ded64eb8a..8c03369d5 100644 --- a/packages/vee-validate/src/index.ts +++ b/packages/vee-validate/src/index.ts @@ -18,3 +18,5 @@ export { useIsFormTouched } from './useIsFormTouched'; export { useIsFormValid } from './useIsFormValid'; export { useValidateForm } from './useValidateForm'; export { useSubmitCount } from './useSubmitCount'; +export { useFieldValue } from './useFieldValue'; +export { useFormValues } from './useFormValues'; diff --git a/packages/vee-validate/src/useFieldValue.ts b/packages/vee-validate/src/useFieldValue.ts new file mode 100644 index 000000000..a74909c74 --- /dev/null +++ b/packages/vee-validate/src/useFieldValue.ts @@ -0,0 +1,21 @@ +import { computed, inject, unref } from 'vue'; +import { FieldContext, FormSymbol } from './symbols'; +import { MaybeReactive } from './types'; +import { getFromPath, injectWithSelf } from './utils'; + +/** + * Gives access to a field's current value + */ +export function useFieldValue(path?: MaybeReactive) { + const form = injectWithSelf(FormSymbol); + // We don't want to use self injected context as it doesn't make sense + const field = path ? undefined : inject(FieldContext); + + return computed(() => { + if (path) { + return getFromPath(form?.values, unref(path)) as TValue | undefined; + } + + return field?.value?.value as TValue | undefined; + }); +} diff --git a/packages/vee-validate/src/useFormValues.ts b/packages/vee-validate/src/useFormValues.ts new file mode 100644 index 000000000..d828c69ad --- /dev/null +++ b/packages/vee-validate/src/useFormValues.ts @@ -0,0 +1,18 @@ +import { computed } from 'vue'; +import { FormSymbol } from './symbols'; +import { FormContext } from './types'; +import { injectWithSelf, warn } from './utils'; + +/** + * Gives access to a form's values + */ +export function useFormValues = Record>() { + const form = injectWithSelf(FormSymbol) as FormContext | undefined; + if (!form) { + warn('No vee-validate
or `useForm` was detected in the component tree'); + } + + return computed(() => { + return form?.values || ({} as Partial); + }); +} diff --git a/packages/vee-validate/tests/useFieldValue.spec.ts b/packages/vee-validate/tests/useFieldValue.spec.ts new file mode 100644 index 000000000..e78cb64e6 --- /dev/null +++ b/packages/vee-validate/tests/useFieldValue.spec.ts @@ -0,0 +1,113 @@ +import flushPromises from 'flush-promises'; +import { useField, useFieldValue, useForm } from '@/vee-validate'; +import { mountWithHoc, setValue } from './helpers'; +import { defineComponent } from 'vue'; + +describe('useFieldValue()', () => { + const REQUIRED_MESSAGE = 'Field is required'; + const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); + + test('gives access to a single field value', async () => { + mountWithHoc({ + setup() { + useForm(); + const { value } = useField('test', validate); + const currValue = useFieldValue('test'); + + return { + value, + currValue, + }; + }, + template: ` + + {{ currValue }} + `, + }); + + await flushPromises(); + const input = document.querySelector('input'); + const valueSpan = document.querySelector('span'); + const inputValue = '1234'; + setValue(input as any, inputValue); + await flushPromises(); + expect(valueSpan?.textContent).toBe(inputValue); + }); + + test('gives access to a single field value in a child component with specifying a path', async () => { + const CustomErrorComponent = defineComponent({ + template: '{{ value }}', + setup() { + const value = useFieldValue(); + + return { + value, + }; + }, + }); + mountWithHoc({ + components: { + CustomErrorComponent, + }, + setup() { + useForm(); + const { value } = useField('test', validate); + + return { + value, + }; + }, + template: ` + + + `, + }); + + await flushPromises(); + const input = document.querySelector('input'); + const valueSpan = document.querySelector('span'); + const inputValue = '1234'; + setValue(input as any, inputValue); + await flushPromises(); + expect(valueSpan?.textContent).toBe(inputValue); + }); + + test('returns undefined if field not found', async () => { + mountWithHoc({ + setup() { + useForm(); + const value = useFieldValue('something'); + + return { + value, + }; + }, + template: ` + {{ value }} + `, + }); + + await flushPromises(); + const error = document.querySelector('span'); + expect(error?.textContent).toBe(''); + }); + + test('returns undefined if form is not found', async () => { + mountWithHoc({ + setup() { + const value = useFieldValue('something'); + + return { + value, + }; + }, + template: ` + {{ value }} + `, + }); + + await flushPromises(); + const error = document.querySelector('span'); + expect(error?.textContent).toBe(''); + }); +}); diff --git a/packages/vee-validate/tests/useFormValues.spec.ts b/packages/vee-validate/tests/useFormValues.spec.ts new file mode 100644 index 000000000..95eca01f0 --- /dev/null +++ b/packages/vee-validate/tests/useFormValues.spec.ts @@ -0,0 +1,58 @@ +import flushPromises from 'flush-promises'; +import { useField, useForm, useFormValues } from '@/vee-validate'; +import { mountWithHoc, setValue } from './helpers'; + +describe('useFormValues()', () => { + const REQUIRED_MESSAGE = 'Field is required'; + const validate = (val: any) => (val ? true : REQUIRED_MESSAGE); + + test('gives access to all form values', async () => { + mountWithHoc({ + setup() { + useForm(); + const { value } = useField('test', validate); + const values = useFormValues(); + + return { + value, + values, + }; + }, + template: ` + + {{ values.test }} + `, + }); + + await flushPromises(); + const input = document.querySelector('input'); + const valueSpan = document.querySelector('span'); + const inputValue = '1234'; + setValue(input as any, inputValue); + await flushPromises(); + expect(valueSpan?.textContent).toBe(inputValue); + }); + + test('returns empty object and warns if form is not found', async () => { + const spy = jest.spyOn(console, 'warn').mockImplementation(); + + mountWithHoc({ + setup() { + const values = useFormValues(); + + return { + values, + }; + }, + template: ` + {{ values }} + `, + }); + + await flushPromises(); + const valuesSpan = document.querySelector('span'); + expect(valuesSpan?.textContent).toBe('{}'); + expect(spy).toHaveBeenCalled(); + spy.mockRestore(); + }); +});