diff --git a/src/components/Form.js b/src/components/Form.js index 63ccd129f07a0..42bb86e469cdf 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -39,6 +39,9 @@ const propTypes = { /** Server side errors keyed by microtime */ errors: PropTypes.objectOf(PropTypes.string), + + /** Field-specific server side errors keyed by microtime */ + errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)), }), /** Contains draft values for each input in the form */ @@ -90,6 +93,17 @@ class Form extends React.Component { return this.props.formState.error || (typeof latestErrorMessage === 'string' ? latestErrorMessage : ''); } + getFirstErroredInput() { + const hasStateErrors = !_.isEmpty(this.state.errors); + const hasErrorFields = !_.isEmpty(this.props.formState.errorFields); + + if (!hasStateErrors && !hasErrorFields) { + return; + } + + return _.first(_.keys(hasStateErrors ? this.state.erorrs : this.props.formState.errorFields)); + } + submit() { // Return early if the form is already submitting to avoid duplicate submission if (this.props.formState.isLoading) { @@ -116,6 +130,7 @@ class Form extends React.Component { */ validate(values) { FormActions.setErrors(this.props.formID, null); + FormActions.setErrorFields(this.props.formID, null); const validationErrors = this.props.validate(values); if (!_.isObject(validationErrors)) { @@ -181,10 +196,19 @@ class Form extends React.Component { this.state.inputValues[inputID] = child.props.value; } + const errorFields = lodashGet(this.props.formState, 'errorFields', {}); + const fieldErrorMessage = _.chain(errorFields[inputID]) + .keys() + .sortBy() + .reverse() + .map(key => errorFields[inputID][key]) + .first() + .value() || ''; + return React.cloneElement(child, { ref: node => this.inputRefs[inputID] = node, value: this.state.inputValues[inputID], - errorText: this.state.errors[inputID] || '', + errorText: this.state.errors[inputID] || fieldErrorMessage, onBlur: () => { this.setTouchedInput(inputID); this.validate(this.state.inputValues); @@ -223,12 +247,13 @@ class Form extends React.Component { {this.props.isSubmitButtonVisible && ( 0 || Boolean(this.getErrorMessage())} + isAlertVisible={_.size(this.state.errors) > 0 || Boolean(this.getErrorMessage()) || !_.isEmpty(this.props.formState.errorFields)} isLoading={this.props.formState.isLoading} - message={this.getErrorMessage()} + message={_.isEmpty(this.props.formState.errorFields) ? this.getErrorMessage() : null} onSubmit={this.submit} onFixTheErrorsLinkPressed={() => { - const focusKey = _.find(_.keys(this.inputRefs), key => _.keys(this.state.errors).includes(key)); + const errors = !_.isEmpty(this.state.errors) ? this.state.errors : this.props.formState.errorFields; + const focusKey = _.find(_.keys(this.inputRefs), key => _.keys(errors).includes(key)); this.inputRefs[focusKey].focus(); }} containerStyles={[styles.mh0, styles.mt5]} diff --git a/src/libs/actions/FormActions.js b/src/libs/actions/FormActions.js index e81b8d28ace77..ee0f48f7da126 100644 --- a/src/libs/actions/FormActions.js +++ b/src/libs/actions/FormActions.js @@ -16,6 +16,14 @@ function setErrors(formID, errors) { Onyx.merge(formID, {errors}); } +/** + * @param {String} formID + * @param {Object} errorFields + */ +function setErrorFields(formID, errorFields) { + Onyx.merge(formID, {errorFields}); +} + /** * @param {String} formID * @param {Object} draftValues @@ -27,5 +35,6 @@ function setDraftValues(formID, draftValues) { export { setIsLoading, setErrors, + setErrorFields, setDraftValues, };