Skip to content

Commit 8fb543a

Browse files
committed
feat: allow name ref to be a lazy function
1 parent eeccd0c commit 8fb543a

File tree

4 files changed

+63
-11
lines changed

4 files changed

+63
-11
lines changed

packages/vee-validate/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export type MaybeRef<T> = Ref<T> | T;
3030

3131
export type MaybeArray<T> = T | T[];
3232

33+
export type MaybeRefOrLazy<T> = MaybeRef<T> | (() => T);
34+
3335
export interface FieldMeta<TValue> {
3436
touched: boolean;
3537
dirty: boolean;

packages/vee-validate/src/useField.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
FormContext,
2727
PrivateFormContext,
2828
YupSchema,
29+
MaybeRefOrLazy,
2930
} from './types';
3031
import {
3132
normalizeRules,
@@ -40,6 +41,7 @@ import {
4041
withLatest,
4142
isEqual,
4243
isTypedSchema,
44+
lazyToRef,
4345
} from './utils';
4446
import { isCallable } from '../../shared';
4547
import { FieldContextKey, FormContextKey, IS_ABSENT } from './symbols';
@@ -77,19 +79,19 @@ export type RuleExpression<TValue> =
7779
* Creates a field composite.
7880
*/
7981
export function useField<TValue = unknown>(
80-
name: MaybeRef<string>,
82+
path: MaybeRefOrLazy<string>,
8183
rules?: MaybeRef<RuleExpression<TValue>>,
8284
opts?: Partial<FieldOptions<TValue>>
8385
): FieldContext<TValue> {
8486
if (hasCheckedAttr(opts?.type)) {
85-
return useCheckboxField(name, rules, opts);
87+
return useCheckboxField(path, rules, opts);
8688
}
8789

88-
return _useField(name, rules, opts);
90+
return _useField(path, rules, opts);
8991
}
9092

9193
function _useField<TValue = unknown>(
92-
name: MaybeRef<string>,
94+
path: MaybeRefOrLazy<string>,
9395
rules?: MaybeRef<RuleExpression<TValue>>,
9496
opts?: Partial<FieldOptions<TValue>>
9597
): FieldContext<TValue> {
@@ -107,10 +109,11 @@ function _useField<TValue = unknown>(
107109
modelPropName,
108110
syncVModel,
109111
form: controlForm,
110-
} = normalizeOptions(unref(name), opts);
112+
} = normalizeOptions(opts);
111113

112114
const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined;
113115
const form = (controlForm as PrivateFormContext | undefined) || injectedForm;
116+
const name = lazyToRef(path);
114117

115118
// a flag indicating if the field is about to be removed/unmounted.
116119
let markedForRemoval = false;
@@ -402,7 +405,7 @@ function _useField<TValue = unknown>(
402405
/**
403406
* Normalizes partial field options to include the full options
404407
*/
405-
function normalizeOptions<TValue>(name: string, opts: Partial<FieldOptions<TValue>> | undefined): FieldOptions<TValue> {
408+
function normalizeOptions<TValue>(opts: Partial<FieldOptions<TValue>> | undefined): FieldOptions<TValue> {
406409
const defaults = (): Partial<FieldOptions<TValue>> => ({
407410
initialValue: undefined,
408411
validateOnMount: false,
@@ -455,7 +458,7 @@ export function extractRuleFromSchema<TValue>(
455458
}
456459

457460
function useCheckboxField<TValue = unknown>(
458-
name: MaybeRef<string>,
461+
name: MaybeRefOrLazy<string>,
459462
rules?: MaybeRef<RuleExpression<TValue>>,
460463
opts?: Partial<FieldOptions<TValue>>
461464
): FieldContext<TValue> {

packages/vee-validate/src/utils/common.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1-
import { getCurrentInstance, inject, InjectionKey, ref, Ref, warn as vueWarning, watch } from 'vue';
1+
import {
2+
computed,
3+
getCurrentInstance,
4+
inject,
5+
InjectionKey,
6+
isRef,
7+
ref,
8+
Ref,
9+
unref,
10+
warn as vueWarning,
11+
watch,
12+
} from 'vue';
213
import { klona as deepCopy } from 'klona/full';
3-
import { isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
14+
import { isCallable, isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
415
import { isContainerValue, isEmptyContainer, isEqual, isNotNestedPath } from './assertions';
5-
import { PrivateFieldContext } from '../types';
16+
import { MaybeRefOrLazy, PrivateFieldContext } from '../types';
617

718
function cleanupNonNestedPath(path: string) {
819
if (isNotNestedPath(path)) {
@@ -299,3 +310,15 @@ export function computedDeep<TValue = unknown>({ get, set }: { get(): TValue; se
299310

300311
return baseRef;
301312
}
313+
314+
export function unravel<T>(value: MaybeRefOrLazy<T>): T {
315+
if (isCallable(value)) {
316+
return value();
317+
}
318+
319+
return unref(value);
320+
}
321+
322+
export function lazyToRef<T>(value: MaybeRefOrLazy<T>): Ref<T> {
323+
return computed(() => unravel(value));
324+
}

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

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

55
describe('useField()', () => {
@@ -816,4 +816,28 @@ describe('useField()', () => {
816816
expect(form1.values.field).toBe('1');
817817
expect(form2.values.field).toBe('2');
818818
});
819+
820+
test('allows lazy name expressions', async () => {
821+
const nameRef = ref('first');
822+
mountWithHoc({
823+
setup() {
824+
const { name } = useField(() => nameRef.value);
825+
826+
return {
827+
name,
828+
};
829+
},
830+
template: `
831+
<span>{{ name }}</span>
832+
`,
833+
});
834+
835+
const name = document.querySelector('span');
836+
837+
await flushPromises();
838+
expect(name?.textContent).toBe('first');
839+
nameRef.value = 'second';
840+
await flushPromises();
841+
expect(name?.textContent).toBe('second');
842+
});
819843
});

0 commit comments

Comments
 (0)