App: Refactor Form: Support `errors` and `errorFields` returned from the backend, and add possibility to hide the submit the button

Problem

We recently started returning an errors object in onyxData from the API that contain the latest error related to a form submission. The current Form component does not take into account the new key and no server error message gets displayed when we submit incorrect data.

There are also some cases where we need to hide the submit button but this is currently not possible as it is abstracted away from outside components. Example: when adding a bank account from Plaid, we need to hide the button while Plaid is loading and when the user hasn’t selected a Plaid account yet from the picker.

Since there are a few ongoing PRs that depend on the Form component, I figured it might be a good idea to create a separate PR for this and have the PRs merge with main.

cc @luacmartins @marcaaron

Solution

  • Add a getErrorMessage function to Form that extracts the latest error message from the errors object. For API calls that still return an error key, this should still work as the function below checks for both keys.
getErrorMessage() {
    const latestErrorMessage = ErrorUtils.getLatestErrorMessage(this.props.formState);
    return this.props.formState.error || (typeof latestErrorMessage === 'string' ? latestErrorMessage : '');
}
  • Rename setErrorMessage in actions/FormActions.js to setErrors.

  • Add an optional prop isSubmitButtonVisible to Form that controls the visibility of the submit button. This prop should have a default value of true to not break any existing components that use Form.

  • Handle field-specific errors coming from the backend and display them on the inputs that need attention.

  • If there are any errorFields, display the errors on the input and display the fixTheErrors.

childrenWrapperWithProps(children) {
    return React.Children.map(children, (child) => {
        ...
        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,
             defaultValue,
             errorText: this.state.errors[inputID] || fieldErrorMessage,
             ...
        }
    }
}

/** Controls the submit button's visibility */
isSubmitButtonVisible: PropTypes.bool,
...

const defaultProps = {
    isSubmitButtonVisible: true,
    ...
};
...

{this.props.isSubmitButtonVisible && (
<FormAlertWithSubmitButton
    buttonText={this.props.submitButtonText}
    isAlertVisible={_.size(this.state.errors) > 0 || Boolean(this.getErrorMessage())}
    isLoading={this.props.formState.isLoading}
    message={_.isEmpty(this.props.formState.errorFields) ? this.getErrorMessage() : null}

    onSubmit={this.submit}
    onFixTheErrorsLinkPressed={() => {
        this.inputRefs[_.first(_.keys(this.state.errors))].focus();
    }}
    containerStyles={[styles.mh0, styles.mt5]}
/>
)}

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 20 (15 by maintainers)

Most upvoted comments

Totally agree with doing this. A great example of us running into this is here.

Is there is any discussion I can look at about why we made the switch from an error key to the errors object key?

IIRC this is where we discussed it, not sure if there were other discussions around it though.

As right now, I checked the code for occurrences of the errors Onyx key, and could not find where we include more than one item in the array (please correct me if I missed something). So I’m not sure yet how we would handle displaying all of the errors because the current format right now only sends out a single error.

That may be the case now, but our OfflineWithFeedback component does allow multiple errors to be passed and displayed here. So we should have the same behavior for Forms.

There’s also a new addition to passing errors from the backend which is errorFields which should contain any server side errors related to specific inputs. I’m working on including it as well in Form.

👍 IIRC errorFields was explicitly added for Form errors, so we should support that in Form.

@youssef-lr I’m assuming you are also actively working on this - if so, we can add the Internal label

Add a getErrorMessage function to Form that extracts the latest error message from the errors object.

Should we be showing all possible errors messages from the errors object?

Rename setErrorMessage in actions/FormActions.js to setErrors.

👍

Add an optional prop isSubmitButtonVisible to Form that controls the visibility of the submit button. This prop should have a default value of true to not break any existing components that use Form.

This is probably good for the Form case. I think sometimes an alternative can be to figure out how to pass an element you would otherwise toggle with a boolean prop in as a child or prop and then have the parent control it’s visibility by either passing in the button or not. That is sometimes more flexible than a boolean prop - but probably not be worth worrying about in this case (and would be a lot more changes).