Skip to content

Commit

Permalink
fix: correctly mutate deep field array item and trigger validation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm authored Oct 23, 2022
1 parent 70ddc5b commit 267736f
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 8 deletions.
8 changes: 5 additions & 3 deletions packages/vee-validate/src/useFieldArray.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Ref, unref, ref, computed, onBeforeUnmount } from 'vue';
import { Ref, unref, ref, onBeforeUnmount } from 'vue';
import { isNullOrUndefined } from '../../shared';
import { FormContextKey } from './symbols';
import { FieldArrayContext, FieldEntry, MaybeRef, PrivateFieldArrayContext } from './types';
import { getFromPath, injectWithSelf, warn } from './utils';
import { computedDeep, getFromPath, injectWithSelf, warn } from './utils';

export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): FieldArrayContext<TValue> {
const form = injectWithSelf(FormContextKey, undefined);
Expand Down Expand Up @@ -64,7 +64,7 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi

const entry: FieldEntry<TValue> = {
key,
value: computed<TValue>({
value: computedDeep<TValue>({
get() {
const currentValues = getFromPath<TValue[]>(form?.values, unref(arrayPath), []) || [];
const idx = fields.value.findIndex(e => e.key === key);
Expand Down Expand Up @@ -172,7 +172,9 @@ export function useFieldArray<TValue = unknown>(arrayPath: MaybeRef<string>): Fi
if (!Array.isArray(pathValue) || pathValue.length - 1 < idx) {
return;
}

form?.setFieldValue(`${pathName}[${idx}]`, value);
form?.validate({ mode: 'validated-only' });

This comment has been minimized.

Copy link
@acicero93

acicero93 Oct 28, 2022

This seems to validate all fields in a schema, even if they previously haven't been validated. Is this a bug?

}

function prepend(value: TValue) {
Expand Down
40 changes: 37 additions & 3 deletions packages/vee-validate/src/utils/common.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getCurrentInstance, inject, InjectionKey, warn as vueWarning } from 'vue';
import isEqual from 'fast-deep-equal';
import { getCurrentInstance, inject, InjectionKey, ref, Ref, warn as vueWarning, watch } from 'vue';
import { klona as deepCopy } from 'klona/full';
import { isIndex, isNullOrUndefined, isObject, toNumber } from '../../../shared';
import { isContainerValue, isEmptyContainer, isNotNestedPath } from './assertions';
import { isContainerValue, isEmptyContainer, isEqual, isNotNestedPath } from './assertions';
import { PrivateFieldContext } from '../types';

function cleanupNonNestedPath(path: string) {
Expand Down Expand Up @@ -265,3 +265,37 @@ export function withLatest<TFunction extends (...args: any[]) => Promise<any>, T
return result;
};
}

export function computedDeep<TValue = unknown>({ get, set }: { get(): TValue; set(value: TValue): void }): Ref<TValue> {
const baseRef = ref(deepCopy(get())) as Ref<TValue>;

watch(
get,
newValue => {
if (isEqual(newValue, baseRef.value)) {
return;
}

baseRef.value = deepCopy(newValue);
},
{
deep: true,
}
);

watch(
baseRef,
newValue => {
if (isEqual(newValue, get())) {
return;
}

set(deepCopy(newValue));
},
{
deep: true,
}
);

return baseRef;
}
48 changes: 46 additions & 2 deletions packages/vee-validate/tests/useFieldArray.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useForm, useFieldArray } from '@/vee-validate';
import { onMounted } from 'vue';
import { useForm, useFieldArray, FieldEntry } from '@/vee-validate';
import { onMounted, Ref } from 'vue';
import * as yup from 'yup';
import { mountWithHoc, flushPromises } from './helpers';

test('can update a field entry model directly', async () => {
Expand Down Expand Up @@ -30,6 +31,49 @@ test('can update a field entry model directly', async () => {
expect(document.querySelector('p')?.innerHTML).toBe('test');
});

test('can update a field entry deep model directly and validate it', async () => {
let fields!: Ref<FieldEntry<{ name: string }>[]>;
mountWithHoc({
setup() {
const { errors } = useForm({
validateOnMount: true,
validationSchema: yup.object({
users: yup.array().of(
yup.object({
name: yup.string().required(),
})
),
}),
initialValues: {
users: [{ name: '' }],
},
});

fields = useFieldArray<{ name: string }>('users').fields;

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

await flushPromises();
expect(document.querySelector('p')?.innerHTML).toBe('');
expect(document.querySelector('span')?.innerHTML).toBeTruthy();

const item = fields.value[0];
item.value.name = 'test';

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

test('warns when updating a no-longer existing item', async () => {
const spy = jest.spyOn(console, 'warn').mockImplementation();
mountWithHoc({
Expand Down

0 comments on commit 267736f

Please sign in to comment.