Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow passing form control to useField closes #3204 #3923

Merged
merged 1 commit into from
Oct 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions packages/vee-validate/src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
PrivateFieldContext,
SchemaValidationMode,
ValidationOptions,
FormContext,
PrivateFormContext,
} from './types';
import {
normalizeRules,
Expand Down Expand Up @@ -52,10 +54,12 @@ export interface FieldOptions<TValue = unknown> {
checkedValue?: MaybeRef<TValue>;
uncheckedValue?: MaybeRef<TValue>;
label?: MaybeRef<string | undefined>;
controlled?: boolean;
standalone?: boolean;
keepValueOnUnmount?: MaybeRef<boolean | undefined>;
modelPropName?: string;
syncVModel?: boolean;
form?: FormContext;
}

export type RuleExpression<TValue> =
Expand Down Expand Up @@ -95,19 +99,21 @@ function _useField<TValue = unknown>(
label,
validateOnValueUpdate,
uncheckedValue,
standalone,
controlled,
keepValueOnUnmount,
modelPropName,
syncVModel,
form: controlForm,
} = normalizeOptions(unref(name), opts);

const form = !standalone ? injectWithSelf(FormContextKey) : undefined;
const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
const form = (controlForm as PrivateFormContext | undefined) || injectedForm;

// a flag indicating if the field is about to be removed/unmounted.
let markedForRemoval = false;
const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, {
modelValue,
standalone,
form,
});

if (syncVModel) {
Expand Down Expand Up @@ -382,31 +388,32 @@ function _useField<TValue = unknown>(
* Normalizes partial field options to include the full options
*/
function normalizeOptions<TValue>(name: string, opts: Partial<FieldOptions<TValue>> | undefined): FieldOptions<TValue> {
const defaults = () => ({
const defaults = (): Partial<FieldOptions> => ({
initialValue: undefined,
validateOnMount: false,
bails: true,
rules: '',
label: name,
validateOnValueUpdate: true,
standalone: false,
keepValueOnUnmount: undefined,
modelPropName: 'modelValue',
syncVModel: true,
controlled: true,
});

if (!opts) {
return defaults();
return defaults() as FieldOptions<TValue>;
}

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

return {
...defaults(),
...(opts || {}),
controlled: controlled ?? true,
checkedValue,
};
} as FieldOptions<TValue>;
}

/**
Expand Down
18 changes: 7 additions & 11 deletions packages/vee-validate/src/useFieldState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { computed, reactive, ref, Ref, unref, watch } from 'vue';
import { FormContextKey } from './symbols';
import { FieldMeta, FieldState, MaybeRef } from './types';
import { getFromPath, injectWithSelf, isEqual } from './utils';
import { FieldMeta, FieldState, MaybeRef, PrivateFormContext } from './types';
import { getFromPath, isEqual } from './utils';

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

export interface StateInit<TValue = unknown> {
modelValue: MaybeRef<TValue>;
standalone: boolean;
form?: PrivateFormContext;
}

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

Expand Down Expand Up @@ -76,9 +75,8 @@ interface FieldValueComposable<TValue = unknown> {
export function _useFieldValue<TValue = unknown>(
path: MaybeRef<string>,
modelValue?: MaybeRef<TValue>,
shouldInjectForm = true
form?: PrivateFormContext
): FieldValueComposable<TValue> {
const form = shouldInjectForm === true ? injectWithSelf(FormContextKey, undefined) : undefined;
const modelRef = ref(unref(modelValue)) as Ref<TValue>;

function resolveInitialValue() {
Expand Down Expand Up @@ -170,9 +168,7 @@ function _useFieldMeta<TValue>(
/**
* Creates the error message state for the field state
*/
export function _useFieldErrors(path: MaybeRef<string>, shouldInjectForm: boolean) {
const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined;

export function _useFieldErrors(path: MaybeRef<string>, form?: PrivateFormContext) {
function normalizeErrors(messages: string | string[] | null | undefined) {
if (!messages) {
return [];
Expand Down
19 changes: 2 additions & 17 deletions packages/vee-validate/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
}

function createModel<TPath extends keyof TValues>(path: MaybeRef<TPath>) {
const { value } = _useFieldValue<TValues[TPath]>(path as string);
const { value } = _useFieldValue<TValues[TPath]>(path as string, undefined, formCtx as PrivateFormContext);
watch(
value,
() => {
Expand Down Expand Up @@ -782,24 +782,9 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
}

return {
errors,
meta,
values: formValues,
isSubmitting,
submitCount,
validate,
validateField,
...formCtx,
handleReset: () => resetForm(),
resetForm,
handleSubmit,
submitForm,
setFieldError,
setErrors,
setFieldValue,
setValues,
setFieldTouched,
setTouched,
useFieldModel,
};
}

Expand Down
34 changes: 33 additions & 1 deletion packages/vee-validate/tests/useField.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useField, useForm } from '@/vee-validate';
import { FieldContext, FormContext, useField, useForm } from '@/vee-validate';
import { defineComponent, nextTick, onMounted, ref } from 'vue';
import { mountWithHoc, setValue, flushPromises } from './helpers';

Expand Down Expand Up @@ -744,4 +744,36 @@ describe('useField()', () => {
await flushPromises();
expect(error?.textContent).toBe('not b');
});

test('allows explicit forms to be provided via the form option', async () => {
let form1!: FormContext;
let form2!: FormContext;
let field1!: FieldContext;
let field2!: FieldContext;
mountWithHoc({
setup() {
form1 = useForm();
form2 = useForm();
field1 = useField('field', undefined, {
form: form1,
});
field2 = useField('field', undefined, {
form: form2,
});

return {};
},
template: `
<div></div>
`,
});

await flushPromises();
field1.value.value = '1';
field2.value.value = '2';
await flushPromises();

expect(form1.values.field).toBe('1');
expect(form2.values.field).toBe('2');
});
});