Skip to content

Commit

Permalink
feat(core/presentation): Create IFormFieldApi interface and implement…
Browse files Browse the repository at this point in the history
… it in FormField and FormikFormField (spinnaker#6020)
  • Loading branch information
christopherthielen authored Nov 15, 2018
1 parent 2833081 commit c1bfbcd
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 23 deletions.
27 changes: 20 additions & 7 deletions app/scripts/modules/core/src/presentation/forms/FormField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Subject } from 'rxjs';
import { isString } from 'lodash';

import { noop } from 'core/utils';

Expand All @@ -13,6 +14,7 @@ import {
IControlledInputProps,
IFieldLayoutPropsWithoutInput,
IFieldValidationStatus,
IFormFieldApi,
IValidationProps,
} from './interface';

Expand All @@ -32,7 +34,9 @@ interface IFormFieldState {
internalValidators: Validator[];
}

export class FormField extends React.Component<IFormFieldProps, IFormFieldState> {
const ifString = (val: any): string => (isString(val) ? val : undefined);

export class FormField extends React.Component<IFormFieldProps, IFormFieldState> implements IFormFieldApi {
public static defaultProps: Partial<IFormFieldProps> = {
layout: StandardFieldLayout,
validate: noop,
Expand All @@ -50,6 +54,18 @@ export class FormField extends React.Component<IFormFieldProps, IFormFieldState>
private destroy$ = new Subject();
private value$ = new Subject();

public name = () => this.props.name;

public label = () => ifString(this.props.label);

public value = () => this.props.value;

public touched = () => this.props.touched;

public validationMessage = () => ifString(this.props.validationMessage) || ifString(this.state.validationMessage);

public validationStatus = () => this.props.validationStatus || this.state.validationStatus;

private addValidator = (internalValidator: Validator) => {
this.setState(prevState => ({
internalValidators: prevState.internalValidators.concat(internalValidator),
Expand Down Expand Up @@ -85,18 +101,15 @@ export class FormField extends React.Component<IFormFieldProps, IFormFieldState>
public render() {
const { input, layout } = this.props; // ICommonFormFieldProps
const { label, help, required, actions } = this.props; // IFieldLayoutPropsWithoutInput
const { touched, validationMessage: message, validationStatus: status } = this.props; // IValidationProps
const { onChange, onBlur, value, name } = this.props; // IControlledInputProps

const fieldLayoutPropsWithoutInput: IFieldLayoutPropsWithoutInput = { label, help, required, actions };
const controlledInputProps: IControlledInputProps = { onChange, onBlur, value, name };

const validationMessage = message || this.state.validationMessage;
const validationStatus = status || this.state.validationStatus;
const validationProps: IValidationProps = {
touched,
validationMessage,
validationStatus,
touched: this.touched(),
validationMessage: this.validationMessage(),
validationStatus: this.validationStatus(),
addValidator: this.addValidator,
removeValidator: this.removeValidator,
};
Expand Down
59 changes: 43 additions & 16 deletions app/scripts/modules/core/src/presentation/forms/FormikFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import * as React from 'react';
import { isString, isUndefined } from 'lodash';
import { Field, FastField, FieldProps, getIn } from 'formik';

import { Field, FastField, FieldProps, getIn, connect, FormikContext } from 'formik';

import {
ICommonFormFieldProps,
IFieldLayoutPropsWithoutInput,
IFieldValidationStatus,
IFormFieldApi,
IValidationProps,
} from './interface';
import { WatchValue } from '../WatchValue';
import { ICommonFormFieldProps, IFieldLayoutPropsWithoutInput, IValidationProps } from './interface';
import { StandardFieldLayout } from './layouts/index';
import { composeValidators, Validator, Validation } from './Validation';
import { renderContent } from './fields/renderContent';
Expand All @@ -28,19 +34,24 @@ export interface IFormikFieldProps<T> {
onChange?: (value: T, prevValue: T) => void;
}

export interface IFormikFormFieldState {
export interface IFormikFormFieldImplState {
internalValidators: Validator[];
}

export type IFormikFormFieldProps<T> = IFormikFieldProps<T> & ICommonFormFieldProps & IFieldLayoutPropsWithoutInput;
type IFormikFormFieldImplProps<T> = IFormikFormFieldProps<T> & { formik: FormikContext<T> };

const ifString = (val: any): string => (isString(val) ? val : undefined);

export class FormikFormField<T = any> extends React.Component<IFormikFormFieldProps<T>, IFormikFormFieldState> {
export class FormikFormFieldImpl<T = any>
extends React.Component<IFormikFormFieldImplProps<T>, IFormikFormFieldImplState>
implements IFormFieldApi {
public static defaultProps: Partial<IFormikFormFieldProps<any>> = {
layout: StandardFieldLayout,
fastField: true,
};

public state: IFormikFormFieldState = {
public state: IFormikFormFieldImplState = {
internalValidators: [],
};

Expand All @@ -56,27 +67,41 @@ export class FormikFormField<T = any> extends React.Component<IFormikFormFieldPr
}));
};

public name = () => this.props.name;

public label = () => ifString(this.props.label);

public value = () => getIn(this.props.formik.values, this.props.name);

public touched = () => {
const { formik, name, touched } = this.props;
return !isUndefined(touched) ? touched : getIn(formik.values, name);
};

public validationMessage = () => {
const { name, formik, validationMessage } = this.props;
return ifString(validationMessage) || getIn(formik.errors, name);
};

public validationStatus = () => {
return (this.props.validationStatus || (this.validationMessage() ? 'error' : null)) as IFieldValidationStatus;
};

public render() {
const { internalValidators } = this.state;
const { name, validate, onChange } = this.props; // IFormikFieldProps
const { input, layout } = this.props; // ICommonFieldProps
const { label, help, required, actions } = this.props; // IFieldLayoutPropsWithoutInput
const { touched, validationMessage, validationStatus } = this.props; // IValidationProps

const fieldLayoutPropsWithoutInput: IFieldLayoutPropsWithoutInput = { label, help, required, actions };

const render = (props: FieldProps<any>) => {
const { field, form } = props;

const formikError = getIn(form.errors, name);
const message = !isUndefined(validationMessage) ? validationMessage : formikError;
const status = !isUndefined(validationStatus) ? validationStatus : formikError ? 'error' : null;
const isTouched = !isUndefined(touched) ? touched : getIn(form.touched, name);
const { field } = props;

const validationProps: IValidationProps = {
validationMessage: message,
validationStatus: status,
touched: isTouched,
touched: this.touched(),
validationMessage: this.validationMessage(),
validationStatus: this.validationStatus(),
addValidator: this.addValidator,
removeValidator: this.removeValidator,
};
Expand Down Expand Up @@ -115,3 +140,5 @@ export function createFieldValidator<T>(
const labelString = isString(label) ? label : undefined;
return (value: any) => validator(value, labelString);
}

export const FormikFormField = connect(FormikFormFieldImpl);
9 changes: 9 additions & 0 deletions app/scripts/modules/core/src/presentation/forms/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ export interface ICommonFormFieldProps {
input: React.ComponentType<IFormInputProps>;
layout?: React.ComponentType<IFieldLayoutProps>;
}

export interface IFormFieldApi {
name(): string;
label(): string;
value(): any;
touched(): boolean;
validationMessage(): string;
validationStatus(): IFieldValidationStatus;
}

0 comments on commit c1bfbcd

Please sign in to comment.