Skip to content

Commit

Permalink
refactor(core/presentation): Remove validationStatus from forms apis
Browse files Browse the repository at this point in the history
This removes the validationStatus prop from the FormField and FormikFormField components.
These props were previously used to provide an explicit validation status (e.g., 'warning' or 'error') into the Form Components.

The functionality has been replaced with message generator functions in `categories.ts`, i.e., errorMessage('Something is wrong').

Before: `<FormField validationMessage='Something is wrong' validationStatus="error" />`
After: `<FormField validationMessage={errorMessage('Something is wrong')} />`
  • Loading branch information
christopherthielen committed Oct 10, 2019
1 parent eafb077 commit 4c5f532
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 269 deletions.
102 changes: 0 additions & 102 deletions app/scripts/modules/core/src/presentation/forms/FormField.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import * as React from 'react';

import { firstDefined, noop } from 'core/utils';

import { IControlledInputProps, IFormInputProps, IFormInputValidation } from '../inputs';
import { ILayoutProps, LayoutContext } from '../layouts';
import { IValidator, useValidationData } from '../validation';

import { ICommonFormFieldProps } from './interface';
import { createFieldValidator } from './FormikFormField';
import { renderContent } from './renderContent';

import '../forms.less';

export type IFormFieldProps = ICommonFormFieldProps & Partial<IControlledInputProps>;

const { useState, useCallback, useContext, useMemo } = React;

export function FormField(props: IFormFieldProps): JSX.Element {
const { input, layout, label, help, required, actions } = props;
const { value } = props;

// Internal validators are defined by an Input component
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)), []);

// Capture props.validate when the component initializes (to allow for inline arrow functions)
const validate = useMemo(() => props.validate, []);

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

const FieldLayoutFromContext = useContext(LayoutContext);
const inputRenderPropOrNode = firstDefined(input, noop);
const layoutFromContext = (fieldLayoutProps: ILayoutProps) => <FieldLayoutFromContext {...fieldLayoutProps} />;
const layoutRenderPropOrNode = firstDefined(layout, layoutFromContext);

const [hasBlurred, setHasBlurred] = useState(false);
const touched = firstDefined(props.touched, hasBlurred);
const validatorResult = useMemo(() => fieldValidator(value), [fieldValidator, value]);
const validationMessage = firstDefined(props.validationMessage, validatorResult ? validatorResult : undefined);
const validationData = useValidationData(validationMessage, touched);

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

const controlledInputProps: IControlledInputProps = {
value: props.value,
name: props.name || '',
onChange: props.onChange || noop,
onBlur: (e: React.FocusEvent) => {
setHasBlurred(true);
props.onBlur && props.onBlur(e);
},
};

// Render the input
const inputProps: IFormInputProps = { ...controlledInputProps, validation };
const inputElement = renderContent(inputRenderPropOrNode, inputProps);

// Render the layout passing the rendered input in
const layoutProps: ILayoutProps = { label, help, required, actions, validation, input: inputElement };
return <>{renderContent(layoutRenderPropOrNode, layoutProps)}</>;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ICommonFormFieldProps } from 'core/presentation/forms/fields/interface';
import { firstDefined } from 'core/utils';
import * as React from 'react';

import { ValidationMessage } from 'core/validation';

import { FormikFormField, IFormikFieldProps } from '../FormikFormField';
import { ExpressionError, ExpressionInput, ExpressionPreview, ISpelError } from '../inputs';
import { ICommonFormFieldProps, IFieldLayoutPropsWithoutInput } from '../interface';
import { FormikFormField, IFormikFormFieldProps } from './FormikFormField';
import { ExpressionError, ExpressionInput, ExpressionPreview, IExpressionChange, ISpelError } from '../inputs';
import { useValidationData } from '../validation';

export interface IExpressionFieldProps {
placeholder?: string;
Expand All @@ -13,41 +13,36 @@ export interface IExpressionFieldProps {
layout?: ICommonFormFieldProps['layout'];
}

export type IFormikExpressionFieldProps = IExpressionFieldProps &
IFormikFieldProps<string> &
IFieldLayoutPropsWithoutInput;
export type IFormikExpressionFieldProps = IExpressionFieldProps & IFormikFormFieldProps<string>;

export interface IFormikExpressionFieldState {
spelPreview: string;
spelError: ISpelError;
}

export class FormikExpressionField extends React.Component<IFormikExpressionFieldProps, IFormikExpressionFieldState> {
public static defaultProps: Partial<IFormikExpressionFieldProps> = { markdown: false };
public state = { spelPreview: '', spelError: null as ISpelError };

public render() {
const { spelError, spelPreview } = this.state;
const { markdown, context } = this.props;
const { name, label, help, actions, validationMessage: message, validationStatus } = this.props;

const validationMessage =
(message && status && <ValidationMessage type={validationStatus} message={message} />) ||
(spelError && context && <ExpressionError spelError={spelError} />) ||
(spelPreview && <ExpressionPreview spelPreview={spelPreview} markdown={markdown} />);

return (
<FormikFormField
name={name}
input={props => (
<ExpressionInput onExpressionChange={changes => this.setState(changes)} context={context} {...props} />
)}
label={label}
help={help}
actions={actions}
validationMessage={validationMessage}
validationStatus={validationStatus || 'message'}
/>
);
}
export function FormikExpressionField(props: IFormikExpressionFieldProps) {
const { context, name, label, help, actions, validationMessage: message } = props;

const [spelData, setSpelData] = React.useState<IExpressionChange>();

const markdown = firstDefined(props.markdown, false);
const validationNode = useValidationData(message, true);

const validationMessage =
validationNode.messageNode ||
(spelData.spelError && context && <ExpressionError spelError={spelData.spelError} />) ||
(spelData.spelPreview && <ExpressionPreview spelPreview={spelData.spelPreview} markdown={markdown} />);

return (
<FormikFormField
name={name}
input={inputProps => (
<ExpressionInput onExpressionChange={changes => setSpelData(changes)} context={context} {...inputProps} />
)}
label={label}
help={help}
actions={actions}
validationMessage={validationMessage}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { getIn } from 'formik';

import {
CollapsibleSection,
errorMessage,
ExpressionError,
FormikForm,
FormikFormField,
IFieldLayoutProps,
IValidationCategory,
ILayoutProps,
messageMessage,
TextInput,
} from 'core/presentation';
import { ValidationMessage } from 'core/validation';

import { ExpressionInput, ExpressionPreview, ISpelError } from '../inputs';
import { IFormikExpressionFieldProps } from './FormikExpressionField';
Expand Down Expand Up @@ -57,7 +57,7 @@ export class FormikExpressionRegexField extends React.Component<
}
};

const RegexLayout = ({ input }: IFieldLayoutProps) => <div style={{ flex: '1 1 40%' }}> {input} </div>;
const RegexLayout = ({ input }: ILayoutProps) => <div style={{ flex: '1 1 40%' }}> {input} </div>;

return (
<CollapsibleSection
Expand Down Expand Up @@ -108,7 +108,7 @@ export class FormikExpressionRegexField extends React.Component<
}
}

private renderFormField(validationMessage: React.ReactNode, validationStatus: IValidationCategory, regex: string) {
private renderFormField(validationMessage: React.ReactNode, regex: string) {
const { context, placeholder, help, label, actions } = this.props;

return (
Expand All @@ -130,7 +130,6 @@ export class FormikExpressionRegexField extends React.Component<
label={label}
actions={actions}
validationMessage={validationMessage}
validationStatus={validationStatus}
touched={true}
/>
);
Expand All @@ -149,17 +148,15 @@ export class FormikExpressionRegexField extends React.Component<
const regexError = getIn(formik.errors, regexName) || getIn(formik.errors, replaceName);

if (regexError) {
const message = <ValidationMessage type="error" message={regexError} />;
return this.renderFormField(message, 'error', regex);
return this.renderFormField(errorMessage(regexError), regex);
} else if (spelError) {
const message = <ExpressionError spelError={spelError} />;
return this.renderFormField(message, 'error', regex);
return this.renderFormField(<ExpressionError spelError={spelError} />, regex);
} else if (spelPreview) {
const message = this.renderPreview(this.props, spelPreview, regex, replace);
return this.renderFormField(message, 'message', regex);
return this.renderFormField(message, regex);
}

return this.renderFormField(null, 'message', regex);
return this.renderFormField(messageMessage(null), regex);
}}
/>
);
Expand Down
Loading

0 comments on commit 4c5f532

Please sign in to comment.