Skip to content

Commit cc2cb41

Browse files
authored
feat: form and fields values setters (#2949)
1 parent 1389437 commit cc2cb41

7 files changed

Lines changed: 198 additions & 34 deletions

File tree

docs/content/api/form.md

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,19 @@ While not recommended, you can make the `Form` component a renderless component
112112

113113
The default slot gives you access to the following props:
114114

115-
| Scoped Prop | Type | Description |
116-
| :----------- | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
117-
| errors | `Record<string, string>` | The first error message of each field, the object keys are the fields names |
118-
| meta | `Record<string, boolean>` | An aggregate of the [FieldMeta](/api/field#fieldmeta) for the fields within the form |
119-
| values | `Record<string, any>` | The current field values |
120-
| isSubmitting | `boolean` | True while the submission handler for the form is being executed |
121-
| validate | `Function` | Validates the form |
122-
| handleSubmit | `(cb: Function) => Function` | Creates a submission handler that disables the native form submissions and executes the callback if the validation passes |
123-
| handleReset | `Function` | Resets and form and executes any `onReset` listeners on the component |
124-
| submitForm | `Function` | Validates the form and triggers the `submit` event on the form, useful for non-SPA applications |
115+
| Scoped Prop | Type | Description |
116+
| :------------ | :--------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
117+
| errors | `Record<string, string>` | The first error message of each field, the object keys are the fields names |
118+
| meta | `Record<string, boolean>` | An aggregate of the [FieldMeta](/api/field#fieldmeta) for the fields within the form |
119+
| values | `Record<string, any>` | The current field values |
120+
| isSubmitting | `boolean` | True while the submission handler for the form is being executed |
121+
| validate | `Function` | Validates the form |
122+
| handleSubmit | `(cb: Function) => Function` | Creates a submission handler that disables the native form submissions and executes the callback if the validation passes |
123+
| handleReset | `Function` | Resets and form and executes any `onReset` listeners on the component |
124+
| submitForm | `Function` | Validates the form and triggers the `submit` event on the form, useful for non-SPA applications |
125+
| setFieldError | `Function` | Sets an error message on a field |
126+
| setErrors | `Function` | Sets error message for the specified fields |
127+
| setFieldValue | `Function` | Sets a field's value, triggers validation |
128+
| setValues | `Function` | Sets the specified fields values, triggers validation on those fields |
125129

126130
Check the sample above for rendering with scoped slots

docs/content/api/use-form.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ type useForm = (
9696
handleReset: (e: Event) => void; // Resets all fields' errors and meta
9797
handleSubmit: (cb: Function) => () => void; // Creates a submission handler that calls the cb only after successful validation with the form values
9898
submitForm: (e: Event) => void; // Forces submission of a form after successful validation (calls e.target.submit())
99-
setErrors: (errors: Record<string, string>) => void; // Sets error messages for fields
99+
setErrors: (fields: Record<string, string>) => void; // Sets error messages for fields
100100
setFieldError: (field: string, errorMessage: string) => void; // Sets an error message for a field
101+
setFieldValue: (field: string, value: any) => void; // Sets a field value
102+
setValues: (fields: Record<string, any>) => void; // Sets multiple fields values
101103
};
102104
```

docs/content/guide/handling-forms.md

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,14 +372,109 @@ You can use `validateOnMount` prop present on the `<Form />` component to force
372372

373373
The `initialValues` prop on both the `<Form />` 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.
374374

375-
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**.
375+
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)
376376

377377
<doc-tip title="Composition API">
378378

379379
If you are using the composition API with `setup` function, you could create the `initialValues` prop using both [**reactive()**](https://v3.vuejs.org/api/basic-reactivity.html#reactive) and [**ref()**](https://v3.vuejs.org/api/refs-api.html#ref). vee-validate handles both cases.
380380

381381
</doc-tip>
382382

383+
## Setting Form Values
384+
385+
You can set any field's value using either `setFieldValue` or `setValues`, both methods are exposed on the `<Form />` 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.
386+
387+
**Using scoped slot props**
388+
389+
```vue
390+
<Form v-slot="{ setFieldValue, setValues }">
391+
<Field name="email" as="input">
392+
<ErrorMessage name="email" />
393+
394+
<Field name="password" as="input">
395+
<ErrorMessage name="password" />
396+
397+
<button type="button" @click="setFieldValue('email', 'test')">Set Field Value</button>
398+
<button type="button" @click="setValues({ email: 'test', password: 'test12' })">
399+
Set Multiple Values
400+
</button>
401+
</Form>
402+
```
403+
404+
**Using submit callback**
405+
406+
```vue
407+
<template>
408+
<Form @submit="onSubmit">
409+
<Field name="email" as="input">
410+
<ErrorMessage name="email" />
411+
412+
<Field name="password" as="input">
413+
<ErrorMessage name="password" />
414+
415+
<button>Submit</button>
416+
</Form>
417+
</template>
418+
419+
<script>
420+
export default {
421+
// ...
422+
methods :{
423+
onSubmit(values, { form }) {
424+
// Submit the values...
425+
426+
// set single field value
427+
form.setFieldValue('email', 'ummm@example.com');
428+
429+
// set multiple values
430+
form.setValues({
431+
email: 'ummm@example.com',
432+
password: 'P@$$w0Rd',
433+
});
434+
}
435+
}
436+
};
437+
</script>
438+
```
439+
440+
**Using template `$refs`**
441+
442+
```vue
443+
<template>
444+
<Form @submit="onSubmit" ref="myForm">
445+
<Field name="email" as="input">
446+
<ErrorMessage name="email" />
447+
448+
<Field name="password" as="input">
449+
<ErrorMessage name="password" />
450+
451+
<button>Submit</button>
452+
</Form>
453+
</template>
454+
455+
<script>
456+
export default {
457+
// ...
458+
methods :{
459+
onSubmit(values) {
460+
// Submit the values...
461+
462+
// set single field value
463+
this.$refs.myForm.setFieldValue('email', 'ummm@example.com');
464+
465+
// set multiple values
466+
this.$refs.myForm.setValues({
467+
email: 'ummm@example.com',
468+
password: 'P@$$w0Rd',
469+
});
470+
}
471+
}
472+
};
473+
</script>
474+
```
475+
476+
Note that setting any field's value using this way will trigger validation
477+
383478
## Setting Errors Manually
384479

385480
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 `<Form />` component and `useForm()` function allow you to set errors manually.

packages/core/src/Form.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export const Form = defineComponent({
3737
submitForm,
3838
setErrors,
3939
setFieldError,
40+
setFieldValue,
41+
setValues,
4042
} = useForm({
4143
validationSchema: props.validationSchema,
4244
initialValues,
@@ -58,6 +60,8 @@ export const Form = defineComponent({
5860
if (!this.setErrors) {
5961
this.setFieldError = setFieldError;
6062
this.setErrors = setErrors;
63+
this.setFieldValue = setFieldValue;
64+
this.setValues = setValues;
6165
}
6266

6367
const children = normalizeChildren(ctx, {
@@ -71,6 +75,8 @@ export const Form = defineComponent({
7175
submitForm,
7276
setErrors,
7377
setFieldError,
78+
setFieldValue,
79+
setValues,
7480
});
7581

7682
if (!props.as) {

packages/core/src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ export interface FormController {
5757
validateSchema?: (shouldMutate?: boolean) => Promise<Record<string, ValidationResult>>;
5858
setFieldValue: (path: string, value: any) => void;
5959
setFieldError: (field: string, message: string) => void;
60-
setErrors: (errors: Record<string, string>) => void;
60+
setErrors: (fields: Record<string, string>) => void;
61+
setValues: (fields: Record<string, any>) => void;
6162
}
6263

6364
type SubmissionContext = { evt: SubmitEvent; form: FormController };

packages/core/src/useForm.ts

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,40 @@ export function useForm(opts?: FormOptions) {
8181
});
8282
}
8383

84+
/**
85+
* Sets a single field value
86+
*/
87+
function setFieldValue(path: string, value: any) {
88+
const field = fieldsById.value[path];
89+
90+
// Multiple checkboxes, and only one of them got updated
91+
if (Array.isArray(field) && field[0]?.type === 'checkbox' && !Array.isArray(value)) {
92+
const oldVal = getFromPath(formValues, path);
93+
const newVal = Array.isArray(oldVal) ? [...oldVal] : [];
94+
const idx = newVal.indexOf(value);
95+
idx >= 0 ? newVal.splice(idx, 1) : newVal.push(value);
96+
setInPath(formValues, path, newVal);
97+
return;
98+
}
99+
100+
let newValue = value;
101+
// Single Checkbox
102+
if (field?.type === 'checkbox') {
103+
newValue = getFromPath(formValues, path) === value ? undefined : value;
104+
}
105+
106+
setInPath(formValues, path, newValue);
107+
}
108+
109+
/**
110+
* Sets multiple fields values
111+
*/
112+
function setValues(fields: Record<string, any>) {
113+
Object.keys(fields).forEach(field => {
114+
setFieldValue(field, fields[field]);
115+
});
116+
}
117+
84118
// a private ref for all form values
85119
const formValues = reactive<Record<string, any>>({});
86120
const controller: FormController = {
@@ -130,27 +164,8 @@ export function useForm(opts?: FormOptions) {
130164
return validateYupSchema(controller, shouldMutate);
131165
}
132166
: undefined,
133-
setFieldValue(path: string, value: any) {
134-
const field = fieldsById.value[path];
135-
136-
// Multiple checkboxes, and only one of them got updated
137-
if (Array.isArray(field) && field[0]?.type === 'checkbox' && !Array.isArray(value)) {
138-
const oldVal = getFromPath(formValues, path);
139-
const newVal = Array.isArray(oldVal) ? [...oldVal] : [];
140-
const idx = newVal.indexOf(value);
141-
idx >= 0 ? newVal.splice(idx, 1) : newVal.push(value);
142-
setInPath(formValues, path, newVal);
143-
return;
144-
}
145-
146-
let newValue = value;
147-
// Single Checkbox
148-
if (field?.type === 'checkbox') {
149-
newValue = getFromPath(formValues, path) === value ? undefined : value;
150-
}
151-
152-
setInPath(formValues, path, newValue);
153-
},
167+
setFieldValue,
168+
setValues,
154169
setErrors,
155170
setFieldError,
156171
};
@@ -291,6 +306,8 @@ export function useForm(opts?: FormOptions) {
291306
submitForm,
292307
setFieldError,
293308
setErrors,
309+
setFieldValue,
310+
setValues,
294311
};
295312
}
296313

packages/core/tests/Form.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,4 +1216,43 @@ describe('<Form />', () => {
12161216
await flushPromises();
12171217
expect(error.textContent).toBe('WRONG');
12181218
});
1219+
1220+
test('sets individual field value with setFieldValue()', async () => {
1221+
const wrapper = mountWithHoc({
1222+
template: `
1223+
<VForm ref="form">
1224+
<Field id="email" name="email" as="input" />
1225+
</VForm>
1226+
`,
1227+
});
1228+
1229+
await flushPromises();
1230+
const value = 'example@gmail.com';
1231+
const email = wrapper.$el.querySelector('#email');
1232+
(wrapper.$refs as any)?.form.setFieldValue('email', value);
1233+
await flushPromises();
1234+
expect(email.value).toBe(value);
1235+
});
1236+
1237+
test('sets multiple fields values with setValues()', async () => {
1238+
const wrapper = mountWithHoc({
1239+
template: `
1240+
<VForm ref="form">
1241+
<Field id="email" name="email" as="input" />
1242+
<Field id="password" name="password" as="input" />
1243+
</VForm>
1244+
`,
1245+
});
1246+
1247+
await flushPromises();
1248+
const values = {
1249+
email: 'example@gmail.com',
1250+
password: '12345',
1251+
};
1252+
const inputs = wrapper.$el.querySelectorAll('input');
1253+
(wrapper.$refs as any)?.form.setValues(values);
1254+
await flushPromises();
1255+
expect(inputs[0].value).toBe(values.email);
1256+
expect(inputs[1].value).toBe(values.password);
1257+
});
12191258
});

0 commit comments

Comments
 (0)