Skip to content

Commit d79747d

Browse files
committed
feat: expose setters in composition API
1 parent 854cd47 commit d79747d

6 files changed

Lines changed: 113 additions & 11 deletions

File tree

packages/vee-validate/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export interface PrivateFieldComposite<TValue = unknown> {
6161
setValidationState(state: ValidationResult): void;
6262
setTouched(isTouched: boolean): void;
6363
setErrors(message: string | string[]): void;
64+
setValue(value: TValue): void;
6465
}
6566

6667
export type FieldComposable<TValue = unknown> = Omit<PrivateFieldComposite<TValue>, 'idx' | 'fid'>;

packages/vee-validate/src/useField.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ export function useField<TValue = unknown>(
184184
watchValue();
185185
}
186186

187+
function setValue(newValue: TValue) {
188+
value.value = newValue;
189+
}
190+
187191
const field: PrivateFieldComposite<TValue> = {
188192
idx: -1,
189193
fid,
@@ -207,6 +211,7 @@ export function useField<TValue = unknown>(
207211
setValidationState,
208212
setTouched,
209213
setErrors,
214+
setValue,
210215
};
211216

212217
provide(FieldContextSymbol, field);

packages/vee-validate/src/useFieldValue.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,25 @@ export function useFieldValue<TValue = unknown>(path?: MaybeRef<string>) {
1111
// We don't want to use self injected context as it doesn't make sense
1212
const field = path ? undefined : inject(FieldContextSymbol);
1313

14-
return computed(() => {
14+
const value = computed(() => {
1515
if (path) {
16-
return getFromPath(form?.values, unref(path)) as TValue | undefined;
16+
return getFromPath(form?.values, unref(path)) as TValue;
1717
}
1818

19-
return field?.value?.value as TValue | undefined;
19+
return field?.value?.value as TValue;
2020
});
21+
22+
function setValue(newValue: TValue) {
23+
if (path) {
24+
form?.setFieldValue(unref(path), newValue);
25+
return;
26+
}
27+
28+
field?.setValue(newValue);
29+
}
30+
31+
return {
32+
value,
33+
setValue,
34+
};
2135
}

packages/vee-validate/src/useFormValues.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,21 @@ export function useFormValues<TValues extends Record<string, unknown> = Record<s
1212
warn('No vee-validate <Form /> or `useForm` was detected in the component tree');
1313
}
1414

15-
return computed(() => {
15+
const values = computed(() => {
1616
return form?.values || ({} as Partial<TValues>);
1717
});
18+
19+
function setFieldValue(path: string, value: any) {
20+
form?.setFieldValue(path, value);
21+
}
22+
23+
function setValues(values: Partial<TValues>) {
24+
form?.setValues(values as any);
25+
}
26+
27+
return {
28+
values,
29+
setFieldValue,
30+
setValues,
31+
};
1832
}

packages/vee-validate/tests/useFieldValue.spec.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,18 @@ describe('useFieldValue()', () => {
1212
setup() {
1313
useForm();
1414
const { value } = useField('test', validate);
15-
const currValue = useFieldValue('test');
15+
const { value: currValue, setValue } = useFieldValue('test');
1616

1717
return {
1818
value,
1919
currValue,
20+
setValue,
2021
};
2122
},
2223
template: `
2324
<input name="field" v-model="value" />
2425
<span>{{ currValue }}</span>
26+
<button @click="setValue('5')"></button>
2527
`,
2628
});
2729

@@ -32,13 +34,19 @@ describe('useFieldValue()', () => {
3234
setValue(input as any, inputValue);
3335
await flushPromises();
3436
expect(valueSpan?.textContent).toBe(inputValue);
37+
38+
// test value setting
39+
const btn = document.querySelector('button');
40+
btn?.click();
41+
await flushPromises();
42+
expect(input?.value).toBe('5');
3543
});
3644

37-
test('gives access to a single field value in a child component with specifying a path', async () => {
45+
test('gives access to a single field value in a child component without specifying a path', async () => {
3846
const CustomErrorComponent = defineComponent({
3947
template: '<span>{{ value }}</span>',
4048
setup() {
41-
const value = useFieldValue();
49+
const { value } = useFieldValue();
4250

4351
return {
4452
value,
@@ -76,7 +84,7 @@ describe('useFieldValue()', () => {
7684
mountWithHoc({
7785
setup() {
7886
useForm();
79-
const value = useFieldValue('something');
87+
const { value } = useFieldValue('something');
8088

8189
return {
8290
value,
@@ -95,7 +103,7 @@ describe('useFieldValue()', () => {
95103
test('returns undefined if form is not found', async () => {
96104
mountWithHoc({
97105
setup() {
98-
const value = useFieldValue('something');
106+
const { value } = useFieldValue('something');
99107

100108
return {
101109
value,

packages/vee-validate/tests/useFormValues.spec.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('useFormValues()', () => {
1111
setup() {
1212
useForm();
1313
const { value } = useField('test', validate);
14-
const values = useFormValues();
14+
const { values } = useFormValues();
1515

1616
return {
1717
value,
@@ -33,12 +33,72 @@ describe('useFormValues()', () => {
3333
expect(valueSpan?.textContent).toBe(inputValue);
3434
});
3535

36+
test('can set a single field value', async () => {
37+
mountWithHoc({
38+
setup() {
39+
useForm();
40+
const { value } = useField('test', validate);
41+
const { values, setFieldValue } = useFormValues();
42+
43+
return {
44+
value,
45+
values,
46+
setFieldValue,
47+
};
48+
},
49+
template: `
50+
<input name="field" v-model="value" />
51+
<span>{{ values.test }}</span>
52+
<button @click="setFieldValue('test', '5')"></button>
53+
`,
54+
});
55+
56+
await flushPromises();
57+
const btn = document.querySelector('button');
58+
const input = document.querySelector('input');
59+
const valueSpan = document.querySelector('span');
60+
btn?.click();
61+
await flushPromises();
62+
expect(valueSpan?.textContent).toBe('5');
63+
expect(input?.value).toBe('5');
64+
});
65+
66+
test('can set multiple fields values', async () => {
67+
mountWithHoc({
68+
setup() {
69+
useForm();
70+
const { value } = useField('test', validate);
71+
const { values, setValues } = useFormValues();
72+
73+
return {
74+
value,
75+
values,
76+
setValues,
77+
};
78+
},
79+
template: `
80+
<input name="field" v-model="value" />
81+
<span>{{ values.test }}</span>
82+
<button @click="setValues({ 'test': '5' })"></button>
83+
`,
84+
});
85+
86+
await flushPromises();
87+
const btn = document.querySelector('button');
88+
const input = document.querySelector('input');
89+
const valueSpan = document.querySelector('span');
90+
btn?.click();
91+
await flushPromises();
92+
expect(valueSpan?.textContent).toBe('5');
93+
expect(input?.value).toBe('5');
94+
});
95+
3696
test('returns empty object and warns if form is not found', async () => {
3797
const spy = jest.spyOn(console, 'warn').mockImplementation();
3898

3999
mountWithHoc({
40100
setup() {
41-
const values = useFormValues();
101+
const { values } = useFormValues();
42102

43103
return {
44104
values,

0 commit comments

Comments
 (0)