Skip to content

Commit

Permalink
Fix array error overwriting (#308)
Browse files Browse the repository at this point in the history
* Fix array error overwriting

* Changeset

* User error merge in form submit

* Remove imports
  • Loading branch information
AndriusOL authored Jan 25, 2024
1 parent 2059156 commit 225ac9e
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-parents-tan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@reactive-forms/core': patch
---

Fixed bug when error set on array type was overwritten by errors from any item in array (error object was replaced with array)
4 changes: 3 additions & 1 deletion packages/core/src/hooks/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { FormMeta } from '../typings/FormMeta';
import { SubmitAction } from '../typings/SubmitAction';
import { deepRemoveEmpty } from '../utils/deepRemoveEmpty';
import { excludeOverlaps } from '../utils/excludeOverlaps';
import { mergeErrors } from '../utils/mergeErrors';
import { overrideMerge } from '../utils/overrideMerge';
import { runYupSchema } from '../utils/runYupSchema';
import { setNestedValues } from '../utils/setNestedValues';
Expand Down Expand Up @@ -172,7 +173,8 @@ export const useForm = <Values extends object>(initialConfig: FormConfig<Values>
const validateFormFnErrors: FieldError<Values> = validatorResultToError(await validateFormFn?.(values));
const schemaErrors = await runFormValidationSchema(values);

const allErrors = deepRemoveEmpty(merge({}, registryErrors, validateFormFnErrors, schemaErrors)) ?? {};
const allErrors =
deepRemoveEmpty(mergeErrors({}, registryErrors, validateFormFnErrors, schemaErrors)) ?? {};

if (!disablePureFieldsValidation) {
return allErrors as FieldError<Values>;
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/hooks/useValidationRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import invariant from 'tiny-invariant';
import { FieldError } from '../typings/FieldError';
import { FieldValidator } from '../typings/FieldValidator';
import { FunctionArray } from '../utils/FunctionArray';
import { mergeErrors } from '../utils/mergeErrors';
import { UnwrapPromise, validatorResultToError } from '../utils/validatorResultToError';

export type ValidationRegistry = PxthMap<FunctionArray<FieldValidator<unknown>>>;
Expand Down Expand Up @@ -72,8 +73,8 @@ export const useValidationRegistry = (): ValidationRegistryControl => {

for (const path of pathsToValidate) {
const error = await validateField(path, deepGet(values, path));
const newErrors = deepSet({}, path, error);
errors = merge(errors, newErrors);
const newErrors = deepSet({}, path, error) as FieldError<T>;
errors = mergeErrors(errors, newErrors);
}

return errors;
Expand Down
14 changes: 14 additions & 0 deletions packages/core/src/utils/mergeErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import isPlainObject from 'lodash/isPlainObject';
import mergeWith from 'lodash/mergeWith';

const errorsMergeCustomizer = (target: unknown, source: unknown): unknown => {
if (Array.isArray(target) && isPlainObject(source)) {
return mergeWith(target, Object.assign([], source), errorsMergeCustomizer);
}
if (Array.isArray(source) && isPlainObject(target)) {
return mergeWith(source, Object.assign([], target), errorsMergeCustomizer);
}
};

export const mergeErrors = (target: unknown, ...sources: unknown[]) =>
mergeWith(target, ...sources, errorsMergeCustomizer);
41 changes: 41 additions & 0 deletions packages/core/tests/hooks/useForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -486,4 +486,45 @@ describe('should merge errors correctly', () => {
expect(result.current.getFieldError(createPxth(['load_models'])).$error).toBe('Error');
expect(onSubmit).toBeCalledTimes(0);
});

it('Should merge error, set on array item in validateForm, with array error', async () => {
const onSubmit = jest.fn();
type ValueType = { emails: { email: string; checked: boolean }[] };

const { result } = renderHook(() =>
useForm<ValueType>({
initialValues: {
emails: [{ email: '', checked: false }],
},
validateForm: ({ emails }) => {
const errors: FieldError<ValueType> = { emails: [] };
for (let i = 0; i < emails.length; i++) {
if (!emails[i].email) {
errors.emails![i] = { email: { $error: 'EmailInvalid' } };
}
}
return errors;
},
onSubmit,
}),
);

const emailsPath = createPxth<ValueType['emails']>(['emails']);

const emailsFieldValidator = jest.fn((value: ValueType['emails']) =>
!value.some((email) => email.checked) ? 'ArrError' : '',
);

const unregisterEmailsValidator = result.current.registerValidator(emailsPath, emailsFieldValidator);

await act(async () => {
await result.current.submit();
});

expect(result.current.getFieldError(emailsPath).$error).toBe('ArrError');
expect(result.current.getFieldError(createPxth(['emails', '0', 'email'])).$error).toBe('EmailInvalid');
expect(onSubmit).toBeCalledTimes(0);

unregisterEmailsValidator();
});
});
20 changes: 20 additions & 0 deletions packages/core/tests/hooks/useValidationRegistry.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,24 @@ describe('useValidationRegistry', () => {
unregisterRootValidator();
unregisterChildValidator();
});

it('Should merge error, set on array, with array items error', async () => {
const { result } = renderUseValidationRegistry();
type TestType = { array: unknown[] };
const path = createPxth<{ array: unknown[] }>([]);

const arrayValidator = jest.fn(() => 'arrayError');
const arrayItemValidator = jest.fn(() => 'itemError');

const unregisterArrayValidator = result.current.registerValidator(path.array, arrayValidator);
const unregisterArrayItemValidator = result.current.registerValidator(path.array[0], arrayItemValidator);

const errors = (await result.current.validateBranch(path, {})).errors as FieldError<TestType>;

expect(errors.array?.$error).toStrictEqual('arrayError');
expect(errors.array?.[0]).toStrictEqual({ $error: 'itemError' });

unregisterArrayValidator();
unregisterArrayItemValidator();
});
});

0 comments on commit 225ac9e

Please sign in to comment.