import * as React from 'react'; import styled from '@emotion/styled'; import classNames from 'classnames'; import FormContext, {FormContextData} from 'app/components/forms/formContext'; import QuestionTooltip from 'app/components/questionTooltip'; import {Meta} from 'app/types'; import {defined} from 'app/utils'; type Value = string | number | boolean; type DefaultProps = { required?: boolean; disabled?: boolean; hideErrorMessage?: boolean; }; type FormFieldProps = DefaultProps & { name: string; style?: object; label?: React.ReactNode; defaultValue?: any; disabledReason?: string; help?: string | React.ReactNode; className?: string; onChange?: (value: Value) => void; error?: string; value?: Value; meta?: Meta; }; type FormFieldState = { error: string | null; value: Value; }; export default class FormField< Props extends FormFieldProps = FormFieldProps, State extends FormFieldState = FormFieldState > extends React.PureComponent { static defaultProps: DefaultProps = { hideErrorMessage: false, disabled: false, required: false, }; constructor(props: Props, context?: any) { super(props, context); this.state = { error: null, value: this.getValue(props, context), } as State; } componentDidMount() {} UNSAFE_componentWillReceiveProps(nextProps: Props, nextContext: FormContextData) { const newError = this.getError(nextProps, nextContext); if (newError !== this.state.error) { this.setState({error: newError}); } if (this.props.value !== nextProps.value || defined(nextContext.form)) { const newValue = this.getValue(nextProps, nextContext); if (newValue !== this.state.value) { this.setValue(newValue); } } } componentWillUnmount() {} static contextType = FormContext; getValue(props: Props, context: FormContextData) { const form = (context || this.context || {}).form; props = props || this.props; if (defined(props.value)) { return props.value; } if (form && form.data.hasOwnProperty(props.name)) { return defined(form.data[props.name]) ? form.data[props.name] : ''; } return defined(props.defaultValue) ? props.defaultValue : ''; } getError(props: Props, context: FormContextData) { const form = (context || this.context || {}).form; props = props || this.props; if (defined(props.error)) { return props.error; } return (form && form.errors[props.name]) || null; } getId() { return `id-${this.props.name}`; } coerceValue(value: any) { return value; } onChange = (e: React.ChangeEvent) => { const value = e.target.value; this.setValue(value); }; setValue = (value: Value) => { const form = (this.context || {}).form; this.setState( { value, }, () => { const finalValue = this.coerceValue(this.state.value); this.props.onChange?.(finalValue); form?.onFieldChange(this.props.name, finalValue); } ); }; getField() { throw new Error('Must be implemented by child.'); } getClassName(): string { throw new Error('Must be implemented by child.'); } getFinalClassNames() { const {className, required} = this.props; const {error} = this.state; return classNames(className, this.getClassName(), { 'has-error': !!error, required, }); } renderDisabledReason() { const {disabled, disabledReason} = this.props; if (!disabled) { return null; } if (!disabledReason) { return null; } return ; } render() { const {label, hideErrorMessage, help, style} = this.props; const {error} = this.state; const cx = this.getFinalClassNames(); const shouldShowErrorMessage = error && !hideErrorMessage; return (
{label && ( )} {this.getField()} {this.renderDisabledReason()} {defined(help) &&

{help}

} {shouldShowErrorMessage && {error}}
); } } const ErrorMessage = styled('p')` font-size: ${p => p.theme.fontSizeMedium}; color: ${p => p.theme.red300}; `;