Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(manager): mutate error object on field-level validation. #703

Merged
merged 1 commit into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 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 @@ -12,9 +12,10 @@ const FormStateManager: React.ComponentType<FormStateManagerProps> = ({
subscription,
clearedValue,
initialValues,
initializeOnMount
initializeOnMount,
validate
}) => {
const { current: managerApi } = useRef(createManagerApi({ onSubmit, clearOnUnmount, subscription, initialValues, initializeOnMount }));
const { current: managerApi } = useRef(createManagerApi({ onSubmit, clearOnUnmount, validate, subscription, initialValues, initializeOnMount }));

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 @@ -416,6 +416,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,5 +1,6 @@
import AnyObject from './any-object';
import { Subscription } from './use-subscription';
import { FormValidator } from './validate';

export interface FormStateManagerProps {
onSubmit: (values: AnyObject) => void;
Expand All @@ -9,6 +10,7 @@ export interface FormStateManagerProps {
clearedValue?: any;
initialValues?: AnyObject;
initializeOnMount?: boolean;
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should support more types. I remember I was using <FormattedMessage /> to return translated message in Sources and it worked.

Copy link
Member Author

@Hyperkid123 Hyperkid123 Aug 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking about this as well. I think we might have to re-visit this topic for the whole package because the field level errors are locked to strings as well. And also all validator return values.

}

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 @@ -186,6 +187,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 @@ -132,6 +132,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