From 8c0d087a0ae7d7e4bd2f1ca7e2e3d99c36518feb Mon Sep 17 00:00:00 2001 From: Chris Thielen Date: Fri, 18 Oct 2019 15:46:09 -0700 Subject: [PATCH] feat(core/presentation): add revalidate api to IFormInputValidation - Make all fields on IFormInputValidation non-optional --- .../bakeManifest/BakeManifestStageForm.tsx | 1 - .../presentation/forms/fields/FormField.tsx | 9 +++++++-- .../forms/fields/FormikFormField.tsx | 18 +++++++++++++++--- .../forms/inputs/ReactSelectInput.tsx | 4 ++-- .../src/presentation/forms/inputs/interface.ts | 13 +++++++------ .../src/presentation/forms/inputs/utils.ts | 3 +-- .../forms/layouts/ResponsiveFieldLayout.tsx | 3 ++- .../forms/layouts/StandardFieldLayout.tsx | 3 ++- 8 files changed, 36 insertions(+), 18 deletions(-) diff --git a/app/scripts/modules/core/src/pipeline/config/stages/bakeManifest/BakeManifestStageForm.tsx b/app/scripts/modules/core/src/pipeline/config/stages/bakeManifest/BakeManifestStageForm.tsx index 2a2c1c5d866..5fa491d1793 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/bakeManifest/BakeManifestStageForm.tsx +++ b/app/scripts/modules/core/src/pipeline/config/stages/bakeManifest/BakeManifestStageForm.tsx @@ -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()} /> diff --git a/app/scripts/modules/core/src/presentation/forms/fields/FormField.tsx b/app/scripts/modules/core/src/presentation/forms/fields/FormField.tsx index 5d2eab2dad7..913b49fce0b 100644 --- a/app/scripts/modules/core/src/presentation/forms/fields/FormField.tsx +++ b/app/scripts/modules/core/src/presentation/forms/fields/FormField.tsx @@ -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)), []); @@ -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); @@ -40,7 +41,10 @@ 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); @@ -48,6 +52,7 @@ export function FormField(props: IFormFieldProps): JSX.Element { touched, addValidator, removeValidator, + revalidate, ...validationData, }; diff --git a/app/scripts/modules/core/src/presentation/forms/fields/FormikFormField.tsx b/app/scripts/modules/core/src/presentation/forms/fields/FormikFormField.tsx index 17085ca78e9..80caab91b6e 100644 --- a/app/scripts/modules/core/src/presentation/forms/fields/FormikFormField.tsx +++ b/app/scripts/modules/core/src/presentation/forms/fields/FormikFormField.tsx @@ -40,20 +40,32 @@ function FormikFormFieldImpl(props: IFormikFormFieldImplProps) { 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) => { - const validation: IFormInputValidation = { touched, addValidator, removeValidator, ...validationNodeProps }; const inputRenderPropOrNode = firstDefined(input, noop); const layoutFromContext = (fieldLayoutProps: ILayoutProps) => ; const layoutRenderPropOrNode = firstDefined(layout, layoutFromContext); diff --git a/app/scripts/modules/core/src/presentation/forms/inputs/ReactSelectInput.tsx b/app/scripts/modules/core/src/presentation/forms/inputs/ReactSelectInput.tsx index cb9f2cf04b3..fdc30eda417 100644 --- a/app/scripts/modules/core/src/presentation/forms/inputs/ReactSelectInput.tsx +++ b/app/scripts/modules/core/src/presentation/forms/inputs/ReactSelectInput.tsx @@ -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'; @@ -64,7 +64,7 @@ export function ReactSelectInput(props: IReactSelectInputProps) { onChange, onBlur, value, - validation = {}, + validation = {} as IFormInputValidation, stringOptions, options: optionOptions, ignoreAccents: accents, diff --git a/app/scripts/modules/core/src/presentation/forms/inputs/interface.ts b/app/scripts/modules/core/src/presentation/forms/inputs/interface.ts index 6bb80d8d837..78b979a4103 100644 --- a/app/scripts/modules/core/src/presentation/forms/inputs/interface.ts +++ b/app/scripts/modules/core/src/presentation/forms/inputs/interface.ts @@ -21,12 +21,13 @@ export type OmitControlledInputPropsFrom = Omit 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 */ diff --git a/app/scripts/modules/core/src/presentation/forms/inputs/utils.ts b/app/scripts/modules/core/src/presentation/forms/inputs/utils.ts index aca8ef8dc6d..1a1362618ca 100644 --- a/app/scripts/modules/core/src/presentation/forms/inputs/utils.ts +++ b/app/scripts/modules/core/src/presentation/forms/inputs/utils.ts @@ -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', diff --git a/app/scripts/modules/core/src/presentation/forms/layouts/ResponsiveFieldLayout.tsx b/app/scripts/modules/core/src/presentation/forms/layouts/ResponsiveFieldLayout.tsx index 8811151b5d3..ceb13cda2c6 100644 --- a/app/scripts/modules/core/src/presentation/forms/layouts/ResponsiveFieldLayout.tsx +++ b/app/scripts/modules/core/src/presentation/forms/layouts/ResponsiveFieldLayout.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { HelpTextExpandedContext } from 'core/help'; +import { IFormInputValidation } from '../inputs'; import { ValidationMessage } from '../validation'; import { ILayoutProps } from './interface'; @@ -8,7 +9,7 @@ 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; diff --git a/app/scripts/modules/core/src/presentation/forms/layouts/StandardFieldLayout.tsx b/app/scripts/modules/core/src/presentation/forms/layouts/StandardFieldLayout.tsx index e5eadcbe3a4..e359baa7f6c 100644 --- a/app/scripts/modules/core/src/presentation/forms/layouts/StandardFieldLayout.tsx +++ b/app/scripts/modules/core/src/presentation/forms/layouts/StandardFieldLayout.tsx @@ -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);