Skip to content

Commit

Permalink
feat(core/presentation): add revalidate api to IFormInputValidation
Browse files Browse the repository at this point in the history
- Make all fields on IFormInputValidation non-optional
  • Loading branch information
christopherthielen committed Oct 21, 2019
1 parent b384038 commit 8c0d087
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export class BakeManifestStageForm extends React.Component<
this.props.formik.setFieldValue('templateRenderer', o.target.value);
}}
value={stage.templateRenderer}
validation={{}}
stringOptions={this.templateRenderers()}
/>
</StageConfigField>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function FormField(props: IFormFieldProps): JSX.Element {

// Internal validators are defined by an Input component
const [internalValidators, setInternalValidators] = useState([]);
const [revalidateRequestId, setRevalidateRequestId] = useState(0);
const addValidator = useCallback((v: IValidator) => setInternalValidators(list => list.concat(v)), []);
const removeValidator = useCallback((v: IValidator) => setInternalValidators(list => list.filter(x => x !== v)), []);

Expand All @@ -30,7 +31,7 @@ export function FormField(props: IFormFieldProps): JSX.Element {

const fieldValidator = useMemo(
() => createFieldValidator(label, required, [].concat(validate || noop).concat(internalValidators)),
[label, required, validate],
[label, required, validate, internalValidators.length],
);

const FieldLayoutFromContext = useContext(LayoutContext);
Expand All @@ -40,14 +41,18 @@ export function FormField(props: IFormFieldProps): JSX.Element {

const [hasBlurred, setHasBlurred] = useState(false);
const touched = firstDefined(props.touched, hasBlurred);
const validatorResult = useMemo(() => fieldValidator(value), [fieldValidator, value]);

// When called, this bumps a revalidateRequestId which in causes validatorResult to be updated
const revalidate = () => setRevalidateRequestId(prev => prev + 1);
const validatorResult = useMemo(() => fieldValidator(value), [fieldValidator, value, revalidateRequestId]);
const validationMessage = firstDefined(props.validationMessage, validatorResult ? validatorResult : undefined);
const validationData = useValidationData(validationMessage, touched);

const validation: IFormInputValidation = {
touched,
addValidator,
removeValidator,
revalidate,
...validationData,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,32 @@ function FormikFormFieldImpl<T = any>(props: IFormikFormFieldImplProps<T>) {
const { name, onChange, fastField: fastFieldProp } = props;
const { input, layout, label, help, required, actions, validate, validationMessage, touched: touchedProp } = props;

const FieldLayoutFromContext = useContext(LayoutContext);

const formikTouched = getIn(formik.touched, name);
const formikError = getIn(formik.errors, props.name);
const fastField = firstDefined(fastFieldProp, true);
const touched = firstDefined(touchedProp, formikTouched as boolean);

const validationNodeProps = useValidationData(firstDefined(validationMessage, formikError as string), touched);
const FieldLayoutFromContext = useContext(LayoutContext);
const message = firstDefined(validationMessage, formikError as string);
const { hidden, category, messageNode } = useValidationData(message, touched);

const [internalValidators, setInternalValidators] = useState([]);
const addValidator = useCallback((v: IValidator) => setInternalValidators(list => list.concat(v)), []);
const removeValidator = useCallback((v: IValidator) => setInternalValidators(list => list.filter(x => x !== v)), []);
const revalidate = () => formik.validate(formik.values);

const validation: IFormInputValidation = {
touched,
revalidate,
addValidator,
removeValidator,
hidden,
category,
messageNode,
};

const renderField = ({ field }: FieldProps<any>) => {
const validation: IFormInputValidation = { touched, addValidator, removeValidator, ...validationNodeProps };
const inputRenderPropOrNode = firstDefined(input, noop);
const layoutFromContext = (fieldLayoutProps: ILayoutProps) => <FieldLayoutFromContext {...fieldLayoutProps} />;
const layoutRenderPropOrNode = firstDefined(layout, layoutFromContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { isNil } from 'lodash';

import { noop } from 'core/utils';

import { IFormInputProps, OmitControlledInputPropsFrom } from './interface';
import { IFormInputProps, IFormInputValidation, OmitControlledInputPropsFrom } from './interface';
import { createFakeReactSyntheticEvent, isStringArray, orEmptyString } from './utils';
import { StringsAsOptions } from './StringsAsOptions';
import { useValidationData } from '../validation';
Expand Down Expand Up @@ -64,7 +64,7 @@ export function ReactSelectInput(props: IReactSelectInputProps) {
onChange,
onBlur,
value,
validation = {},
validation = {} as IFormInputValidation,
stringOptions,
options: optionOptions,
ignoreAccents: accents,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ export type OmitControlledInputPropsFrom<T> = Omit<T, keyof IControlledInputProp

/** These props are used by Input components, such as TextInput */
export interface IFormInputValidation {
touched?: boolean;
category?: IValidationCategory;
messageNode?: React.ReactNode;
hidden?: boolean;
addValidator?: (validator: IValidator) => void;
removeValidator?: (validator: IValidator) => void;
touched: boolean;
hidden: boolean;
category: IValidationCategory | undefined;
messageNode: React.ReactNode | undefined;
revalidate: () => void;
addValidator: (validator: IValidator) => void;
removeValidator: (validator: IValidator) => void;
}

/** These props are used by Form Input components, such as TextInput */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { IFormInputValidation } from './interface';

export const orEmptyString = (val: any) => (isUndefined(val) ? '' : val);

export const validationClassName = (validation: IFormInputValidation) => {
validation = validation || {};
export const validationClassName = (validation = {} as IFormInputValidation) => {
return classNames({
'ng-dirty': !!validation.touched,
'ng-invalid': validation.category === 'error',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import * as React from 'react';

import { HelpTextExpandedContext } from 'core/help';
import { IFormInputValidation } from '../inputs';
import { ValidationMessage } from '../validation';

import { ILayoutProps } from './interface';

import '../forms.less';

export function ResponsiveFieldLayout(props: ILayoutProps) {
const { label, help, input, actions, validation = {} } = props;
const { label, help, input, actions, validation = {} as IFormInputValidation } = props;
const { hidden, messageNode, category } = validation;
const showLabel = !!label || !!help;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as React from 'react';
import { isUndefined } from 'lodash';
import { ValidationMessage } from '../validation';
import { IFormInputValidation } from '../inputs';
import { ILayoutProps } from './interface';
import './StandardFieldLayout.css';

export function StandardFieldLayout(props: ILayoutProps) {
const { label, help, input, actions, validation = {} } = props;
const { label, help, input, actions, validation = {} as IFormInputValidation } = props;
const { hidden, messageNode, category } = validation;
const showLabel = !isUndefined(label) || !isUndefined(help);

Expand Down

0 comments on commit 8c0d087

Please sign in to comment.