Skip to content

Commit

Permalink
feat(4.6): Allow mutating field array iterable's value property (#3618)…
Browse files Browse the repository at this point in the history
… (#3759)
  • Loading branch information
logaretm authored Apr 29, 2022
1 parent a52f133 commit c3c40e5
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 10 deletions.
4 changes: 2 additions & 2 deletions packages/vee-validate/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComputedRef, DeepReadonly, Ref } from 'vue';
import { ComputedRef, Ref } from 'vue';
import { SchemaOf, AnySchema, AnyObjectSchema } from 'yup';
import { FieldValidationMetaInfo } from '../../shared';

Expand Down Expand Up @@ -58,7 +58,7 @@ export interface FieldEntry<TValue = unknown> {
}

export interface FieldArrayContext<TValue = unknown> {
fields: DeepReadonly<Ref<FieldEntry<TValue>[]>>;
fields: Ref<FieldEntry<TValue>[]>;
remove(idx: number): void;
replace(newArray: TValue[]): void;
update(idx: number, value: TValue): void;
Expand Down
27 changes: 19 additions & 8 deletions packages/vee-validate/src/useFieldArray.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Ref, unref, ref, readonly, computed, onBeforeUnmount } from 'vue';
import { Ref, unref, ref, computed, onBeforeUnmount } from 'vue';
import { isNullOrUndefined } from '../../shared';
import { FormContextKey } from './symbols';
import { FieldArrayContext, FieldEntry, MaybeRef } from './types';
Expand All @@ -13,7 +13,7 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noOp = () => {};
const noOpApi: FieldArrayContext<TValue> = {
fields: readonly(fields),
fields,
remove: noOp,
push: noOp,
swap: noOp,
Expand Down Expand Up @@ -61,11 +61,22 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi

const entry: FieldEntry<TValue> = {
key,
value: computed<TValue>(() => {
const currentValues = getFromPath<TValue[]>(form?.values, unref(arrayPath), []);
const idx = fields.value.findIndex(e => e.key === key);

return idx === -1 ? value : currentValues[idx];
value: computed<TValue>({
get() {
const currentValues = getFromPath<TValue[]>(form?.values, unref(arrayPath), []);
const idx = fields.value.findIndex(e => e.key === key);

return idx === -1 ? value : currentValues[idx];
},
set(value: TValue) {
const idx = fields.value.findIndex(e => e.key === key);
if (idx === -1) {
warn(`Attempting to update a non-existent array item`);
return;
}

update(idx, value);
},
}) as any, // will be auto unwrapped
isFirst: false,
isLast: false,
Expand Down Expand Up @@ -209,7 +220,7 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
});

return {
fields: readonly(fields),
fields,
remove,
push,
swap,
Expand Down
52 changes: 52 additions & 0 deletions packages/vee-validate/tests/FieldArray.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,58 @@ test('can update an item value at a given array index', async () => {
expect(getValue(inputAt(1))).toBe('updated');
});

test('can update an item value directly with .value setter', async () => {
const onSubmit = jest.fn();
mountWithHoc({
setup() {
const initial = {
users: [
{
name: 'first',
},
],
};

return {
initial,
onSubmit,
};
},
template: `
<VForm :initial-values="initial" @submit="onSubmit">
<FieldArray name="users" v-slot="{ fields }">
<div v-for="(field, idx) in fields" :key="field.key">
<input v-model="fields[idx].value.name" />
</div>
</FieldArray>
<button>Submit</button>
</VForm>
`,
});

await flushPromises();
const inputAt = (idx: number) => (document.querySelectorAll('input') || [])[idx] as HTMLInputElement;

expect(getValue(inputAt(0))).toBe('first');
setValue(inputAt(0), 'updated');
await flushPromises();
expect(getValue(inputAt(0))).toBe('updated');
document.querySelector('button')?.click();
await flushPromises();

expect(onSubmit).toHaveBeenLastCalledWith(
expect.objectContaining({
users: [
{
name: 'updated',
},
],
}),
expect.anything()
);
});

test('adds items to the start of the array with prepend()', async () => {
const onSubmit = jest.fn();
mountWithHoc({
Expand Down
59 changes: 59 additions & 0 deletions packages/vee-validate/tests/useFieldArray.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useForm, useFieldArray } from '@/vee-validate';
import { onMounted } from 'vue';
import { mountWithHoc, flushPromises } from './helpers';

test('can update a field entry model directly', async () => {
mountWithHoc({
setup() {
useForm({
initialValues: {
users: ['1'],
},
});

const { fields } = useFieldArray('users');
onMounted(() => {
const item = fields.value[0];
item.value = 'test';
});

return {
fields,
};
},
template: `
<p>{{ fields[0].value }}</p>
`,
});

await flushPromises();
expect(document.querySelector('p')?.innerHTML).toBe('test');
});

test('warns when updating a no-longer existing item', async () => {
const spy = jest.spyOn(console, 'warn').mockImplementation();
mountWithHoc({
setup() {
useForm({
initialValues: {
users: ['1'],
},
});

const { remove, fields } = useFieldArray('users');
onMounted(() => {
const item = fields.value[0];
remove(0);
item.value = 'test';
});
},
template: `
<div></div>
`,
});

await flushPromises();

expect(spy).toHaveBeenCalled();
spy.mockRestore();
});

0 comments on commit c3c40e5

Please sign in to comment.