Skip to content

Commit

Permalink
feat(manager): mutate error object on field-level validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hyperkid123 committed Aug 11, 2020
1 parent ee2343d commit cebb62f
Show file tree
Hide file tree
Showing 8 changed files with 52 additions and 11 deletions.
11 changes: 9 additions & 2 deletions packages/form-state-manager/src/files/form-state-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ import createManagerApi from '../utils/manager-api';

import FormStateManagerProps from '../types/form-state-manager';

const FormStateManager: React.ComponentType<FormStateManagerProps> = ({ children, onSubmit, clearOnUnmount, subscription, clearedValue }) => {
const { current: managerApi } = useRef(createManagerApi({ onSubmit, clearOnUnmount, subscription }));
const FormStateManager: React.ComponentType<FormStateManagerProps> = ({
children,
onSubmit,
clearOnUnmount,
subscription,
clearedValue,
validate
}) => {
const { current: managerApi } = useRef(createManagerApi({ onSubmit, clearOnUnmount, subscription, validate }));

const { change, handleSubmit, registerField, unregisterField, getState, getFieldValue, getFieldState, blur, focus } = managerApi();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,31 @@ describe('useSubscription', () => {
wrapper.update();
expect(wrapper.find(SpyComponent).prop('meta')).toEqual(expect.objectContaining({ error: undefined, valid: true, invalid: false }));
});

it('should set form level error key on sync validation', async () => {
const managerApi = createManagerApi({});
const subscriberProps = {
name: 'sync-validate',
validate: fooValidator
};
const wrapper = mount(<DummyComponent managerApi={managerApi} subscriberProps={subscriberProps} />);
const input = wrapper.find('input');
expect(managerApi().errors).toEqual({});

await act(async () => {
input.simulate('change', { target: { value: 'foo' } });
});
expect(managerApi().errors).toEqual({
'sync-validate': 'error'
});

await act(async () => {
input.simulate('change', { target: { value: 'bar' } });
});
expect(managerApi().errors).toEqual({
'sync-validate': undefined
});
});
});

describe('clearedValue', () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/form-state-manager/src/types/form-state-manager.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import AnyObject from './any-object';
import { Subscription } from './use-subscription';
import { FormValidator } from './validate';

export interface FormStateManagerProps {
onSubmit: (values: AnyObject) => void;
children: (props: AnyObject) => React.ReactNode;
clearOnUnmount?: boolean;
subscription?: Subscription;
clearedValue?: any;
validate?: FormValidator;
}

export default FormStateManagerProps;
1 change: 1 addition & 0 deletions packages/form-state-manager/src/types/manager-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface ManagerState {
getFieldValue: GetFieldValue;
getFieldState: GetFieldState;
registerAsyncValidator: (validator: Promise<unknown>) => void;
updateError: (name: string, error: string | undefined) => void;
updateValid: UpdateValid;
rerender: Rerender;
registeredFields: Array<string>;
Expand Down
12 changes: 6 additions & 6 deletions packages/form-state-manager/src/types/validate.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import AnyObject from './any-object';
import { ManagerApi } from './manager-api';

export interface FormLevelError {
[key: string]: string;
}

export type Validator = (value: any, allValues: AnyObject) => undefined | string | Promise<string | undefined>;

export type FormValidator = (allValues: AnyObject) => undefined | string | Promise<string | undefined>;
export type FormValidator = (allValues: AnyObject) => FormLevelError | Promise<FormLevelError>;

export type FieldLevelValidator = (
validator: Validator,
Expand All @@ -12,8 +16,4 @@ export type FieldLevelValidator = (
managerApi: ManagerApi
) => string | undefined | Promise<string | undefined>;

export type FormLevelValidator = (
validator: FormValidator,
allValues: AnyObject,
managerApi: ManagerApi
) => string | undefined | Promise<string | undefined>;
export type FormLevelValidator = (validator: FormValidator, allValues: AnyObject, managerApi: ManagerApi) => FormLevelError | Promise<FormLevelError>;
7 changes: 6 additions & 1 deletion packages/form-state-manager/src/utils/manager-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import set from 'lodash/set';
import CreateManagerApi, { ManagerState, ManagerApi, AsyncWatcher, AsyncWatcherRecord, Rerender } from '../types/manager-api';
import AnyObject from '../types/any-object';
import FieldConfig from '../types/field-config';
import { formLevelValidator } from './validate';
import { formLevelValidator, isPromise } from './validate';

const isLast = (fieldListeners: AnyObject, name: string) => fieldListeners?.[name]?.count === 1;

Expand Down Expand Up @@ -55,6 +55,7 @@ const createManagerApi: CreateManagerApi = ({ onSubmit, clearOnUnmount, initiali
getFieldValue,
getFieldState,
registerAsyncValidator,
updateError,
updateValid,
rerender,
registeredFields: [],
Expand Down Expand Up @@ -184,6 +185,10 @@ const createManagerApi: CreateManagerApi = ({ onSubmit, clearOnUnmount, initiali
state.submitting = submitting;
}

function updateError(name: string, error: string | undefined = undefined): void {
state.errors[name] = error;
}

function registerAsyncValidator(validator: Promise<unknown>) {
asyncWatcherApi.registerValidator(validator);
}
Expand Down
1 change: 1 addition & 0 deletions packages/form-state-manager/src/utils/use-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const useSubscription = ({
validating: false
}
}));
formOptions().updateError(name, isValid ? undefined : error);
};

const finalClearedValue = Object.prototype.hasOwnProperty.call(props, 'clearedValue') ? props.clearedValue : rest.clearedValue;
Expand Down
4 changes: 2 additions & 2 deletions packages/form-state-manager/src/utils/validate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import AnyObject from '../types/any-object';
import { FieldLevelValidator, FormLevelValidator } from '../types/validate';
import { FieldLevelValidator, FormLevelValidator, FormLevelError } from '../types/validate';

export const isPromise = (obj: AnyObject | PromiseLike<unknown> | string | undefined): boolean =>
!!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
Expand All @@ -19,7 +19,7 @@ export const fieldLevelValidator: FieldLevelValidator = (validator, value, allVa
export const formLevelValidator: FormLevelValidator = (validator, allValues, managerApi) => {
const result = validator(allValues);
if (isPromise(result)) {
const asyncResult = result as Promise<string | undefined>;
const asyncResult = result as Promise<FormLevelError>;
managerApi().registerAsyncValidator(asyncResult);
asyncResult.then(() => undefined).catch((error) => error);
return result;
Expand Down

0 comments on commit cebb62f

Please sign in to comment.