Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support displaying backend errors in Form #11465

Merged
merged 8 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -90,6 +93,17 @@ class Form extends React.Component {
return this.props.formState.error || (typeof latestErrorMessage === 'string' ? latestErrorMessage : '');
}

getFirstErroredInput() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just come across this randomly but this is missing a JSDoc and should have an @returns

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.state.erorrs is a typo ?

}

submit() {
// Return early if the form is already submitting to avoid duplicate submission
if (this.props.formState.isLoading) {
Expand All @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -223,12 +247,13 @@ class Form extends React.Component {
{this.props.isSubmitButtonVisible && (
<FormAlertWithSubmitButton
buttonText={this.props.submitButtonText}
isAlertVisible={_.size(this.state.errors) > 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}
Copy link
Contributor Author

@youssef-lr youssef-lr Sep 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured I should add this check here to avoid displaying a duplicate message on both the error field above the submit button and below the errored input. Instead, we will display the fixTheErrors message and the field error message below the input.

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]}
Expand Down
9 changes: 9 additions & 0 deletions src/libs/actions/FormActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,5 +35,6 @@ function setDraftValues(formID, draftValues) {
export {
setIsLoading,
setErrors,
setErrorFields,
setDraftValues,
};