Skip to content

Commit 4c59d63

Browse files
authored
feat: allow passing form control to useField closes #3204 (#3923)
1 parent dbceb6d commit 4c59d63

File tree

4 files changed

+57
-37
lines changed

4 files changed

+57
-37
lines changed

packages/vee-validate/src/useField.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
PrivateFieldContext,
2424
SchemaValidationMode,
2525
ValidationOptions,
26+
FormContext,
27+
PrivateFormContext,
2628
} from './types';
2729
import {
2830
normalizeRules,
@@ -52,10 +54,12 @@ export interface FieldOptions<TValue = unknown> {
5254
checkedValue?: MaybeRef<TValue>;
5355
uncheckedValue?: MaybeRef<TValue>;
5456
label?: MaybeRef<string | undefined>;
57+
controlled?: boolean;
5558
standalone?: boolean;
5659
keepValueOnUnmount?: MaybeRef<boolean | undefined>;
5760
modelPropName?: string;
5861
syncVModel?: boolean;
62+
form?: FormContext;
5963
}
6064

6165
export type RuleExpression<TValue> =
@@ -95,19 +99,21 @@ function _useField<TValue = unknown>(
9599
label,
96100
validateOnValueUpdate,
97101
uncheckedValue,
98-
standalone,
102+
controlled,
99103
keepValueOnUnmount,
100104
modelPropName,
101105
syncVModel,
106+
form: controlForm,
102107
} = normalizeOptions(unref(name), opts);
103108

104-
const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
109+
const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
110+
const form = (controlForm as PrivateFormContext | undefined) || injectedForm;
105111

106112
// a flag indicating if the field is about to be removed/unmounted.
107113
let markedForRemoval = false;
108114
const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
109115
modelValue,
110-
standalone,
116+
form,
111117
});
112118

113119
if (syncVModel) {
@@ -382,31 +388,32 @@ function _useField<TValue = unknown>(
382388
* Normalizes partial field options to include the full options
383389
*/
384390
function normalizeOptions<TValue>(name: string, opts: Partial<FieldOptions<TValue>> | undefined): FieldOptions<TValue> {
385-
const defaults = () => ({
391+
const defaults = (): Partial<FieldOptions> => ({
386392
initialValue: undefined,
387393
validateOnMount: false,
388394
bails: true,
389-
rules: '',
390395
label: name,
391396
validateOnValueUpdate: true,
392-
standalone: false,
393397
keepValueOnUnmount: undefined,
394398
modelPropName: 'modelValue',
395399
syncVModel: true,
400+
controlled: true,
396401
});
397402

398403
if (!opts) {
399-
return defaults();
404+
return defaults() as FieldOptions<TValue>;
400405
}
401406

402407
// TODO: Deprecate this in next major release
403408
const checkedValue = 'valueProp' in opts ? opts.valueProp : opts.checkedValue;
409+
const controlled = 'standalone' in opts ? !opts.standalone : opts.controlled;
404410

405411
return {
406412
...defaults(),
407413
...(opts || {}),
414+
controlled: controlled ?? true,
408415
checkedValue,
409-
};
416+
} as FieldOptions<TValue>;
410417
}
411418

412419
/**

packages/vee-validate/src/useFieldState.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { computed, reactive, ref, Ref, unref, watch } from 'vue';
2-
import { FormContextKey } from './symbols';
3-
import { FieldMeta, FieldState, MaybeRef } from './types';
4-
import { getFromPath, injectWithSelf, isEqual } from './utils';
2+
import { FieldMeta, FieldState, MaybeRef, PrivateFormContext } from './types';
3+
import { getFromPath, isEqual } from './utils';
54

65
export interface StateSetterInit<TValue = unknown> extends FieldState<TValue> {
76
initialValue: TValue;
@@ -20,7 +19,7 @@ export interface FieldStateComposable<TValue = unknown> {
2019

2120
export interface StateInit<TValue = unknown> {
2221
modelValue: MaybeRef<TValue>;
23-
standalone: boolean;
22+
form?: PrivateFormContext;
2423
}
2524

2625
let ID_COUNTER = 0;
@@ -29,8 +28,8 @@ export function useFieldState<TValue = unknown>(
2928
path: MaybeRef<string>,
3029
init: Partial<StateInit<TValue>>
3130
): FieldStateComposable<TValue> {
32-
const { value, initialValue, setInitialValue } = _useFieldValue<TValue>(path, init.modelValue, !init.standalone);
33-
const { errorMessage, errors, setErrors } = _useFieldErrors(path, !init.standalone);
31+
const { value, initialValue, setInitialValue } = _useFieldValue<TValue>(path, init.modelValue, init.form);
32+
const { errorMessage, errors, setErrors } = _useFieldErrors(path, init.form);
3433
const meta = _useFieldMeta(value, initialValue, errors);
3534
const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER;
3635

@@ -76,9 +75,8 @@ interface FieldValueComposable<TValue = unknown> {
7675
export function _useFieldValue<TValue = unknown>(
7776
path: MaybeRef<string>,
7877
modelValue?: MaybeRef<TValue>,
79-
shouldInjectForm = true
78+
form?: PrivateFormContext
8079
): FieldValueComposable<TValue> {
81-
const form = shouldInjectForm === true ? injectWithSelf(FormContextKey, undefined) : undefined;
8280
const modelRef = ref(unref(modelValue)) as Ref<TValue>;
8381

8482
function resolveInitialValue() {
@@ -170,9 +168,7 @@ function _useFieldMeta<TValue>(
170168
/**
171169
* Creates the error message state for the field state
172170
*/
173-
export function _useFieldErrors(path: MaybeRef<string>, shouldInjectForm: boolean) {
174-
const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;
175-
171+
export function _useFieldErrors(path: MaybeRef<string>, form?: PrivateFormContext) {
176172
function normalizeErrors(messages: string | string[] | null | undefined) {
177173
if (!messages) {
178174
return [];

packages/vee-validate/src/useForm.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
355355
}
356356

357357
function createModel<TPath extends keyof TValues>(path: MaybeRef<TPath>) {
358-
const { value } = _useFieldValue<TValues[TPath]>(path as string);
358+
const { value } = _useFieldValue<TValues[TPath]>(path as string, undefined, formCtx as PrivateFormContext);
359359
watch(
360360
value,
361361
() => {
@@ -782,24 +782,9 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
782782
}
783783

784784
return {
785-
errors,
786-
meta,
787-
values: formValues,
788-
isSubmitting,
789-
submitCount,
790-
validate,
791-
validateField,
785+
...formCtx,
792786
handleReset: () => resetForm(),
793-
resetForm,
794-
handleSubmit,
795787
submitForm,
796-
setFieldError,
797-
setErrors,
798-
setFieldValue,
799-
setValues,
800-
setFieldTouched,
801-
setTouched,
802-
useFieldModel,
803788
};
804789
}
805790

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useField, useForm } from '@/vee-validate';
1+
import { FieldContext, FormContext, useField, useForm } from '@/vee-validate';
22
import { defineComponent, nextTick, onMounted, ref } from 'vue';
33
import { mountWithHoc, setValue, flushPromises } from './helpers';
44

@@ -744,4 +744,36 @@ describe('useField()', () => {
744744
await flushPromises();
745745
expect(error?.textContent).toBe('not b');
746746
});
747+
748+
test('allows explicit forms to be provided via the form option', async () => {
749+
let form1!: FormContext;
750+
let form2!: FormContext;
751+
let field1!: FieldContext;
752+
let field2!: FieldContext;
753+
mountWithHoc({
754+
setup() {
755+
form1 = useForm();
756+
form2 = useForm();
757+
field1 = useField('field', undefined, {
758+
form: form1,
759+
});
760+
field2 = useField('field', undefined, {
761+
form: form2,
762+
});
763+
764+
return {};
765+
},
766+
template: `
767+
<div></div>
768+
`,
769+
});
770+
771+
await flushPromises();
772+
field1.value.value = '1';
773+
field2.value.value = '2';
774+
await flushPromises();
775+
776+
expect(form1.values.field).toBe('1');
777+
expect(form2.values.field).toBe('2');
778+
});
747779
});

0 commit comments

Comments
 (0)