diff --git a/docs/content/api/form.md b/docs/content/api/form.md index b01b802a5..5f0d920d3 100644 --- a/docs/content/api/form.md +++ b/docs/content/api/form.md @@ -112,15 +112,19 @@ While not recommended, you can make the `Form` component a renderless component The default slot gives you access to the following props: -| Scoped Prop | Type | Description | -| :----------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------ | -| errors | `Record` | The first error message of each field, the object keys are the fields names | -| meta | `Record` | An aggregate of the [FieldMeta](/api/field#fieldmeta) for the fields within the form | -| values | `Record` | The current field values | -| isSubmitting | `boolean` | True while the submission handler for the form is being executed | -| validate | `Function` | Validates the form | -| handleSubmit | `(cb: Function) => Function` | Creates a submission handler that disables the native form submissions and executes the callback if the validation passes | -| handleReset | `Function` | Resets and form and executes any `onReset` listeners on the component | -| submitForm | `Function` | Validates the form and triggers the `submit` event on the form, useful for non-SPA applications | +| Scoped Prop | Type | Description | +| :------------ | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------ | +| errors | `Record` | The first error message of each field, the object keys are the fields names | +| meta | `Record` | An aggregate of the [FieldMeta](/api/field#fieldmeta) for the fields within the form | +| values | `Record` | The current field values | +| isSubmitting | `boolean` | True while the submission handler for the form is being executed | +| validate | `Function` | Validates the form | +| handleSubmit | `(cb: Function) => Function` | Creates a submission handler that disables the native form submissions and executes the callback if the validation passes | +| handleReset | `Function` | Resets and form and executes any `onReset` listeners on the component | +| submitForm | `Function` | Validates the form and triggers the `submit` event on the form, useful for non-SPA applications | +| setFieldError | `Function` | Sets an error message on a field | +| setErrors | `Function` | Sets error message for the specified fields | +| setFieldValue | `Function` | Sets a field's value, triggers validation | +| setValues | `Function` | Sets the specified fields values, triggers validation on those fields | Check the sample above for rendering with scoped slots diff --git a/docs/content/api/use-form.md b/docs/content/api/use-form.md index 7e436414a..36bdf2cd7 100644 --- a/docs/content/api/use-form.md +++ b/docs/content/api/use-form.md @@ -96,7 +96,9 @@ type useForm = ( handleReset: (e: Event) => void; // Resets all fields' errors and meta handleSubmit: (cb: Function) => () => void; // Creates a submission handler that calls the cb only after successful validation with the form values submitForm: (e: Event) => void; // Forces submission of a form after successful validation (calls e.target.submit()) - setErrors: (errors: Record) => void; // Sets error messages for fields + setErrors: (fields: Record) => void; // Sets error messages for fields setFieldError: (field: string, errorMessage: string) => void; // Sets an error message for a field + setFieldValue: (field: string, value: any) => void; // Sets a field value + setValues: (fields: Record) => void; // Sets multiple fields values }; ``` diff --git a/docs/content/guide/handling-forms.md b/docs/content/guide/handling-forms.md index 80c29ff65..8f49aeac5 100644 --- a/docs/content/guide/handling-forms.md +++ b/docs/content/guide/handling-forms.md @@ -372,7 +372,7 @@ You can use `validateOnMount` prop present on the `
` component to force The `initialValues` prop on both the `` component and `useForm()` function can reactive value, meaning you can change the initial values after your component was created/mounted which is very useful if you are populating form fields from external API. -Note that **only the pristine fields will be updated**. In other words, **only the fields that were not manipulated by the user will be updated**. +Note that **only the pristine fields will be updated**. In other words, **only the fields that were not manipulated by the user will be updated**. For information on how to set the values for all fields regardless of their dirty status check the following [Setting Form Values section](#setting-form-values) @@ -380,6 +380,101 @@ If you are using the composition API with `setup` function, you could create the +## Setting Form Values + +You can set any field's value using either `setFieldValue` or `setValues`, both methods are exposed on the `` component scoped slot props, and in `useForm` return value, and as instance methods if so you can call them with template `$refs` and for an added convenience you can call them in the submit handler callback. + +**Using scoped slot props** + +```vue + + + + + + + + + + +``` + +**Using submit callback** + +```vue + + + +``` + +**Using template `$refs`** + +```vue + + + +``` + +Note that setting any field's value using this way will trigger validation + ## Setting Errors Manually Quite often you will find yourself unable to replicate some validation rules on the client-side due to natural limitations. For example a `unique` email validation is complex to implement on the client-side, which is why the `
` component and `useForm()` function allow you to set errors manually. diff --git a/packages/core/src/Form.ts b/packages/core/src/Form.ts index 901f4b032..af12c9cc3 100644 --- a/packages/core/src/Form.ts +++ b/packages/core/src/Form.ts @@ -37,6 +37,8 @@ export const Form = defineComponent({ submitForm, setErrors, setFieldError, + setFieldValue, + setValues, } = useForm({ validationSchema: props.validationSchema, initialValues, @@ -58,6 +60,8 @@ export const Form = defineComponent({ if (!this.setErrors) { this.setFieldError = setFieldError; this.setErrors = setErrors; + this.setFieldValue = setFieldValue; + this.setValues = setValues; } const children = normalizeChildren(ctx, { @@ -71,6 +75,8 @@ export const Form = defineComponent({ submitForm, setErrors, setFieldError, + setFieldValue, + setValues, }); if (!props.as) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 9f379b817..9e2a18230 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -57,7 +57,8 @@ export interface FormController { validateSchema?: (shouldMutate?: boolean) => Promise>; setFieldValue: (path: string, value: any) => void; setFieldError: (field: string, message: string) => void; - setErrors: (errors: Record) => void; + setErrors: (fields: Record) => void; + setValues: (fields: Record) => void; } type SubmissionContext = { evt: SubmitEvent; form: FormController }; diff --git a/packages/core/src/useForm.ts b/packages/core/src/useForm.ts index 4b50e5425..3d20572cd 100644 --- a/packages/core/src/useForm.ts +++ b/packages/core/src/useForm.ts @@ -81,6 +81,40 @@ export function useForm(opts?: FormOptions) { }); } + /** + * Sets a single field value + */ + function setFieldValue(path: string, value: any) { + const field = fieldsById.value[path]; + + // Multiple checkboxes, and only one of them got updated + if (Array.isArray(field) && field[0]?.type === 'checkbox' && !Array.isArray(value)) { + const oldVal = getFromPath(formValues, path); + const newVal = Array.isArray(oldVal) ? [...oldVal] : []; + const idx = newVal.indexOf(value); + idx >= 0 ? newVal.splice(idx, 1) : newVal.push(value); + setInPath(formValues, path, newVal); + return; + } + + let newValue = value; + // Single Checkbox + if (field?.type === 'checkbox') { + newValue = getFromPath(formValues, path) === value ? undefined : value; + } + + setInPath(formValues, path, newValue); + } + + /** + * Sets multiple fields values + */ + function setValues(fields: Record) { + Object.keys(fields).forEach(field => { + setFieldValue(field, fields[field]); + }); + } + // a private ref for all form values const formValues = reactive>({}); const controller: FormController = { @@ -130,27 +164,8 @@ export function useForm(opts?: FormOptions) { return validateYupSchema(controller, shouldMutate); } : undefined, - setFieldValue(path: string, value: any) { - const field = fieldsById.value[path]; - - // Multiple checkboxes, and only one of them got updated - if (Array.isArray(field) && field[0]?.type === 'checkbox' && !Array.isArray(value)) { - const oldVal = getFromPath(formValues, path); - const newVal = Array.isArray(oldVal) ? [...oldVal] : []; - const idx = newVal.indexOf(value); - idx >= 0 ? newVal.splice(idx, 1) : newVal.push(value); - setInPath(formValues, path, newVal); - return; - } - - let newValue = value; - // Single Checkbox - if (field?.type === 'checkbox') { - newValue = getFromPath(formValues, path) === value ? undefined : value; - } - - setInPath(formValues, path, newValue); - }, + setFieldValue, + setValues, setErrors, setFieldError, }; @@ -291,6 +306,8 @@ export function useForm(opts?: FormOptions) { submitForm, setFieldError, setErrors, + setFieldValue, + setValues, }; } diff --git a/packages/core/tests/Form.spec.ts b/packages/core/tests/Form.spec.ts index a1616e9b6..400f4fb94 100644 --- a/packages/core/tests/Form.spec.ts +++ b/packages/core/tests/Form.spec.ts @@ -1216,4 +1216,43 @@ describe('', () => { await flushPromises(); expect(error.textContent).toBe('WRONG'); }); + + test('sets individual field value with setFieldValue()', async () => { + const wrapper = mountWithHoc({ + template: ` + + + + `, + }); + + await flushPromises(); + const value = 'example@gmail.com'; + const email = wrapper.$el.querySelector('#email'); + (wrapper.$refs as any)?.form.setFieldValue('email', value); + await flushPromises(); + expect(email.value).toBe(value); + }); + + test('sets multiple fields values with setValues()', async () => { + const wrapper = mountWithHoc({ + template: ` + + + + + `, + }); + + await flushPromises(); + const values = { + email: 'example@gmail.com', + password: '12345', + }; + const inputs = wrapper.$el.querySelectorAll('input'); + (wrapper.$refs as any)?.form.setValues(values); + await flushPromises(); + expect(inputs[0].value).toBe(values.email); + expect(inputs[1].value).toBe(values.password); + }); });