Skip to content

Commit

Permalink
feat: allow name ref to be a lazy function
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Mar 21, 2023
1 parent eeccd0c commit 8fb543a
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 11 deletions.
2 changes: 2 additions & 0 deletions packages/vee-validate/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type MaybeRef<T> = Ref<T> | T;

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

export type MaybeRefOrLazy<T> = MaybeRef<T> | (() => T);

This comment has been minimized.


export interface FieldMeta<TValue> {
touched: boolean;
dirty: boolean;
Expand Down
17 changes: 10 additions & 7 deletions packages/vee-validate/src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
FormContext,
PrivateFormContext,
YupSchema,
MaybeRefOrLazy,
} from './types';
import {
normalizeRules,
Expand All @@ -40,6 +41,7 @@ import {
withLatest,
isEqual,
isTypedSchema,
lazyToRef,
} from './utils';
import { isCallable } from '../../shared';
import { FieldContextKey, FormContextKey, IS_ABSENT } from './symbols';
Expand Down Expand Up @@ -77,19 +79,19 @@ export type RuleExpression<TValue> =
* Creates a field composite.
*/
export function useField<TValue = unknown>(
name: MaybeRef<string>,
path: MaybeRefOrLazy<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>
): FieldContext<TValue> {
if (hasCheckedAttr(opts?.type)) {
return useCheckboxField(name, rules, opts);
return useCheckboxField(path, rules, opts);
}

return _useField(name, rules, opts);
return _useField(path, rules, opts);
}

function _useField<TValue = unknown>(
name: MaybeRef<string>,
path: MaybeRefOrLazy<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>
): FieldContext<TValue> {
Expand All @@ -107,10 +109,11 @@ function _useField<TValue = unknown>(
modelPropName,
syncVModel,
form: controlForm,
} = normalizeOptions(unref(name), opts);
} = normalizeOptions(opts);

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

// a flag indicating if the field is about to be removed/unmounted.
let markedForRemoval = false;
Expand Down Expand Up @@ -402,7 +405,7 @@ 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> {
function normalizeOptions<TValue>(opts: Partial<FieldOptions<TValue>> | undefined): FieldOptions<TValue> {
const defaults = (): Partial<FieldOptions<TValue>> => ({
initialValue: undefined,
validateOnMount: false,
Expand Down Expand Up @@ -455,7 +458,7 @@ export function extractRuleFromSchema<TValue>(
}

function useCheckboxField<TValue = unknown>(
name: MaybeRef<string>,
name: MaybeRefOrLazy<string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>
): FieldContext<TValue> {
Expand Down
29 changes: 26 additions & 3 deletions packages/vee-validate/src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { getCurrentInstance, inject, InjectionKey, ref, Ref, warn as vueWarning, watch } from 'vue';
import {
computed,
getCurrentInstance,
inject,
InjectionKey,
isRef,
ref,
Ref,
unref,
warn as vueWarning,
watch,
} from 'vue';
import { klona as deepCopy } from 'klona/full';
import { isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
import { isCallable, isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
import { isContainerValue, isEmptyContainer, isEqual, isNotNestedPath } from './assertions';
import { PrivateFieldContext } from '../types';
import { MaybeRefOrLazy, PrivateFieldContext } from '../types';

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

return baseRef;
}

export function unravel<T>(value: MaybeRefOrLazy<T>): T {

This comment has been minimized.

Copy link
@DrJume

DrJume Mar 24, 2023

VueUse has a similar helper for this use case:
https://vueuse.org/shared/resolveRef

if (isCallable(value)) {
return value();
}

return unref(value);
}

export function lazyToRef<T>(value: MaybeRefOrLazy<T>): Ref<T> {
return computed(() => unravel(value));
}
26 changes: 25 additions & 1 deletion packages/vee-validate/tests/useField.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FieldContext, FormContext, useField, useForm } from '@/vee-validate';
import { defineComponent, nextTick, onMounted, ref } from 'vue';
import { defineComponent, onMounted, ref } from 'vue';
import { mountWithHoc, setValue, flushPromises } from './helpers';

describe('useField()', () => {
Expand Down Expand Up @@ -816,4 +816,28 @@ describe('useField()', () => {
expect(form1.values.field).toBe('1');
expect(form2.values.field).toBe('2');
});

test('allows lazy name expressions', async () => {
const nameRef = ref('first');
mountWithHoc({
setup() {
const { name } = useField(() => nameRef.value);

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

const name = document.querySelector('span');

await flushPromises();
expect(name?.textContent).toBe('first');
nameRef.value = 'second';
await flushPromises();
expect(name?.textContent).toBe('second');
});
});

0 comments on commit 8fb543a

Please sign in to comment.