Skip to content

Commit

Permalink
feat: expose setters in composition API
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Jul 6, 2021
1 parent 854cd47 commit d79747d
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/vee-validate/src/types.ts
Expand Up @@ -61,6 +61,7 @@ export interface PrivateFieldComposite<TValue = unknown> {
setValidationState(state: ValidationResult): void;
setTouched(isTouched: boolean): void;
setErrors(message: string | string[]): void;
setValue(value: TValue): void;
}

export type FieldComposable<TValue = unknown> = Omit<PrivateFieldComposite<TValue>, 'idx' | 'fid'>;
Expand Down
5 changes: 5 additions & 0 deletions packages/vee-validate/src/useField.ts
Expand Up @@ -184,6 +184,10 @@ export function useField<TValue = unknown>(
watchValue();
}

function setValue(newValue: TValue) {
value.value = newValue;
}

const field: PrivateFieldComposite<TValue> = {
idx: -1,
fid,
Expand All @@ -207,6 +211,7 @@ export function useField<TValue = unknown>(
setValidationState,
setTouched,
setErrors,
setValue,
};

provide(FieldContextSymbol, field);
Expand Down
20 changes: 17 additions & 3 deletions packages/vee-validate/src/useFieldValue.ts
Expand Up @@ -11,11 +11,25 @@ export function useFieldValue<TValue = unknown>(path?: MaybeRef<string>) {
// We don't want to use self injected context as it doesn't make sense
const field = path ? undefined : inject(FieldContextSymbol);

return computed(() => {
const value = computed(() => {
if (path) {
return getFromPath(form?.values, unref(path)) as TValue | undefined;
return getFromPath(form?.values, unref(path)) as TValue;
}

return field?.value?.value as TValue | undefined;
return field?.value?.value as TValue;
});

function setValue(newValue: TValue) {
if (path) {
form?.setFieldValue(unref(path), newValue);
return;
}

field?.setValue(newValue);
}

return {
value,
setValue,
};
}
16 changes: 15 additions & 1 deletion packages/vee-validate/src/useFormValues.ts
Expand Up @@ -12,7 +12,21 @@ export function useFormValues<TValues extends Record<string, unknown> = Record<s
warn('No vee-validate <Form /> or `useForm` was detected in the component tree');
}

return computed(() => {
const values = computed(() => {
return form?.values || ({} as Partial<TValues>);
});

function setFieldValue(path: string, value: any) {
form?.setFieldValue(path, value);
}

function setValues(values: Partial<TValues>) {
form?.setValues(values as any);
}

return {
values,
setFieldValue,
setValues,
};
}
18 changes: 13 additions & 5 deletions packages/vee-validate/tests/useFieldValue.spec.ts
Expand Up @@ -12,16 +12,18 @@ describe('useFieldValue()', () => {
setup() {
useForm();
const { value } = useField('test', validate);
const currValue = useFieldValue('test');
const { value: currValue, setValue } = useFieldValue('test');

return {
value,
currValue,
setValue,
};
},
template: `
<input name="field" v-model="value" />
<span>{{ currValue }}</span>
<button @click="setValue('5')"></button>
`,
});

Expand All @@ -32,13 +34,19 @@ describe('useFieldValue()', () => {
setValue(input as any, inputValue);
await flushPromises();
expect(valueSpan?.textContent).toBe(inputValue);

// test value setting
const btn = document.querySelector('button');
btn?.click();
await flushPromises();
expect(input?.value).toBe('5');
});

test('gives access to a single field value in a child component with specifying a path', async () => {
test('gives access to a single field value in a child component without specifying a path', async () => {
const CustomErrorComponent = defineComponent({
template: '<span>{{ value }}</span>',
setup() {
const value = useFieldValue();
const { value } = useFieldValue();

return {
value,
Expand Down Expand Up @@ -76,7 +84,7 @@ describe('useFieldValue()', () => {
mountWithHoc({
setup() {
useForm();
const value = useFieldValue('something');
const { value } = useFieldValue('something');

return {
value,
Expand All @@ -95,7 +103,7 @@ describe('useFieldValue()', () => {
test('returns undefined if form is not found', async () => {
mountWithHoc({
setup() {
const value = useFieldValue('something');
const { value } = useFieldValue('something');

return {
value,
Expand Down
64 changes: 62 additions & 2 deletions packages/vee-validate/tests/useFormValues.spec.ts
Expand Up @@ -11,7 +11,7 @@ describe('useFormValues()', () => {
setup() {
useForm();
const { value } = useField('test', validate);
const values = useFormValues();
const { values } = useFormValues();

return {
value,
Expand All @@ -33,12 +33,72 @@ describe('useFormValues()', () => {
expect(valueSpan?.textContent).toBe(inputValue);
});

test('can set a single field value', async () => {
mountWithHoc({
setup() {
useForm();
const { value } = useField('test', validate);
const { values, setFieldValue } = useFormValues();

return {
value,
values,
setFieldValue,
};
},
template: `
<input name="field" v-model="value" />
<span>{{ values.test }}</span>
<button @click="setFieldValue('test', '5')"></button>
`,
});

await flushPromises();
const btn = document.querySelector('button');
const input = document.querySelector('input');
const valueSpan = document.querySelector('span');
btn?.click();
await flushPromises();
expect(valueSpan?.textContent).toBe('5');
expect(input?.value).toBe('5');
});

test('can set multiple fields values', async () => {
mountWithHoc({
setup() {
useForm();
const { value } = useField('test', validate);
const { values, setValues } = useFormValues();

return {
value,
values,
setValues,
};
},
template: `
<input name="field" v-model="value" />
<span>{{ values.test }}</span>
<button @click="setValues({ 'test': '5' })"></button>
`,
});

await flushPromises();
const btn = document.querySelector('button');
const input = document.querySelector('input');
const valueSpan = document.querySelector('span');
btn?.click();
await flushPromises();
expect(valueSpan?.textContent).toBe('5');
expect(input?.value).toBe('5');
});

test('returns empty object and warns if form is not found', async () => {
const spy = jest.spyOn(console, 'warn').mockImplementation();

mountWithHoc({
setup() {
const values = useFormValues();
const { values } = useFormValues();

return {
values,
Expand Down

0 comments on commit d79747d

Please sign in to comment.