Form is responsible to change customErrorText

FieldTextInput: touch flag couldn't be tracked reliably in responses with errors
This commit is contained in:
Vesa Luusua 2018-04-19 16:34:31 +03:00
parent a7b9a08a86
commit 7c58442aae
5 changed files with 143 additions and 96 deletions

View file

@ -41,9 +41,9 @@ class FieldTextInputComponent extends Component {
// Error message and input error styles are only shown if the
// field has been touched and the validation has failed.
const hasError = touched && invalid && errorText;
const hasError = !!customErrorText || !!(touched && invalid && error);
const fieldMeta = { touched, error: errorText };
const fieldMeta = { touched: hasError, error: errorText };
const inputClasses =
inputRootClass ||
@ -116,6 +116,6 @@ class FieldTextInput extends Component {
render() {
return <Field component={FieldTextInputComponent} {...this.props} />;
}
};
}
export default FieldTextInput;

View file

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { Form as FinalForm } from 'react-final-form';
import { isEqual } from 'lodash';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import * as validators from '../../util/validators';
@ -24,6 +25,7 @@ class ContactDetailsFormComponent extends Component {
this.state = { showVerificationEmailSentMessage: false };
this.emailSentTimeoutId = null;
this.handleResendVerificationEmail = this.handleResendVerificationEmail.bind(this);
this.submittedValues = {};
}
componentWillUnmount() {
@ -54,7 +56,6 @@ class ContactDetailsFormComponent extends Component {
currentUser,
formId,
handleSubmit,
submitting,
inProgress,
intl,
invalid,
@ -97,6 +98,7 @@ class ContactDetailsFormComponent extends Component {
sendVerificationEmailError
);
const emailTouched = this.submittedValues.email !== values.email;
const emailTakenErrorText = isChangeEmailTakenError(saveEmailError)
? intl.formatMessage({ id: 'ContactDetailsForm.emailTakenError' })
: null;
@ -221,6 +223,7 @@ class ContactDetailsFormComponent extends Component {
const passwordFailedMessage = intl.formatMessage({
id: 'ContactDetailsForm.passwordFailed',
});
const passwordTouched = this.submittedValues.currentPassword !== values.currentPassword;
const passwordErrorText = isChangeEmailWrongPassword(saveEmailError)
? passwordFailedMessage
: null;
@ -255,11 +258,22 @@ class ContactDetailsFormComponent extends Component {
}
const classes = classNames(rootClassName || css.root, className);
const submittedOnce = Object.keys(this.submittedValues).length > 0;
const pristineSinceLastSubmit = submittedOnce && isEqual(values, this.submittedValues);
const submitDisabled =
invalid || submitting || inProgress || !(emailChanged || phoneNumberChanged);
invalid ||
pristineSinceLastSubmit ||
inProgress ||
!(emailChanged || phoneNumberChanged);
return (
<Form className={classes} onSubmit={handleSubmit}>
<Form
className={classes}
onSubmit={e => {
this.submittedValues = values;
handleSubmit(e);
}}
>
<div className={css.contactDetailsSection}>
<FieldTextInput
type="email"
@ -268,7 +282,7 @@ class ContactDetailsFormComponent extends Component {
label={emailLabel}
placeholder={emailPlaceholder}
validate={validators.composeValidators(emailRequired, emailValid)}
customErrorText={emailTakenErrorText}
customErrorText={emailTouched ? null : emailTakenErrorText}
/>
{emailVerifiedInfo}
<FieldPhoneNumberInput
@ -297,7 +311,7 @@ class ContactDetailsFormComponent extends Component {
label={passwordLabel}
placeholder={passwordPlaceholder}
validate={passwordValidators}
customErrorText={passwordErrorText}
customErrorText={passwordTouched ? null : passwordErrorText}
/>
</div>
<div className={css.bottomWrapper}>

View file

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { Form as FinalForm } from 'react-final-form';
import { isEqual } from 'lodash';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import * as validators from '../../util/validators';
@ -18,6 +19,7 @@ class PasswordChangeFormComponent extends Component {
constructor(props) {
super(props);
this.resetTimeoutId = null;
this.submittedValues = {};
}
componentWillUnmount() {
window.clearTimeout(this.resetTimeoutId);
@ -34,13 +36,13 @@ class PasswordChangeFormComponent extends Component {
changePasswordError,
currentUser,
handleSubmit,
submitting,
inProgress,
intl,
invalid,
pristine,
ready,
reset,
form,
values,
} = fieldRenderProps;
const user = ensureCurrentUser(currentUser);
@ -103,6 +105,7 @@ class PasswordChangeFormComponent extends Component {
const passwordFailedMessage = intl.formatMessage({
id: 'PasswordChangeForm.passwordFailed',
});
const passwordTouched = this.submittedValues.currentPassword !== values.currentPassword;
const passwordErrorText = isChangePasswordWrongPassword(changePasswordError)
? passwordFailedMessage
: null;
@ -118,16 +121,19 @@ class PasswordChangeFormComponent extends Component {
</span>
) : null;
const submittedOnce = Object.keys(this.submittedValues).length > 0;
const pristineSinceLastSubmit = submittedOnce && isEqual(values, this.submittedValues);
const classes = classNames(rootClassName || css.root, className);
const submitDisabled = invalid || submitting || inProgress;
const submitDisabled = invalid || pristineSinceLastSubmit || inProgress;
return (
<Form
className={classes}
onSubmit={values => {
handleSubmit(values)
onSubmit={e => {
this.submittedValues = values;
handleSubmit(e)
.then(() => {
this.resetTimeoutId = window.setTimeout(reset, RESET_TIMEOUT);
this.resetTimeoutId = window.setTimeout(form.reset, RESET_TIMEOUT);
})
.catch(() => {
// Error is handled in duck file already.
@ -171,7 +177,7 @@ class PasswordChangeFormComponent extends Component {
passwordMinLength,
passwordMaxLength
)}
customErrorText={passwordErrorText}
customErrorText={passwordTouched ? null : passwordErrorText}
/>
</div>
<div className={css.bottomWrapper}>

View file

@ -1,8 +1,9 @@
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
import { Form as FinalForm } from 'react-final-form';
import { isEqual } from 'lodash';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import * as validators from '../../util/validators';
@ -11,90 +12,116 @@ import { Form, PrimaryButton, FieldTextInput, NamedLink } from '../../components
import css from './PasswordRecoveryForm.css';
const PasswordRecoveryFormComponent = props => (
<FinalForm
{...props}
render={fieldRenderProps => {
const {
rootClassName,
className,
formId,
handleSubmit,
pristine,
submitting,
initialValues,
intl,
inProgress,
recoveryError,
} = fieldRenderProps;
class PasswordRecoveryFormComponent extends Component {
constructor(props) {
super(props);
this.submittedValues = {};
}
// email
const emailLabel = intl.formatMessage({
id: 'PasswordRecoveryForm.emailLabel',
});
const emailPlaceholder = intl.formatMessage({
id: 'PasswordRecoveryForm.emailPlaceholder',
});
const emailRequiredMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailRequired',
});
const emailNotFoundMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailNotFound',
});
const emailInvalidMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailInvalid',
});
render() {
return (
<FinalForm
{...this.props}
render={fieldRenderProps => {
const {
rootClassName,
className,
formId,
handleSubmit,
pristine,
initialValues,
intl,
inProgress,
recoveryError,
values,
} = fieldRenderProps;
const emailRequired = validators.required(emailRequiredMessage);
const emailValid = validators.emailFormatValid(emailInvalidMessage);
// email
const emailLabel = intl.formatMessage({
id: 'PasswordRecoveryForm.emailLabel',
});
const emailPlaceholder = intl.formatMessage({
id: 'PasswordRecoveryForm.emailPlaceholder',
});
const emailRequiredMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailRequired',
});
const emailNotFoundMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailNotFound',
});
const emailInvalidMessage = intl.formatMessage({
id: 'PasswordRecoveryForm.emailInvalid',
});
// In case a given email is not found, pass a custom error message
// to be rendered with the input component
const customErrorText = isPasswordRecoveryEmailNotFoundError(recoveryError)
? emailNotFoundMessage
: null;
const initialEmail = initialValues ? initialValues.email : null;
const emailRequired = validators.required(emailRequiredMessage);
const emailValid = validators.emailFormatValid(emailInvalidMessage);
const classes = classNames(rootClassName || css.root, className);
const submitInProgress = submitting || inProgress;
const submitDisabled = (pristine && !initialEmail) || submitInProgress;
// In case a given email is not found, pass a custom error message
// to be rendered with the input component
const customErrorText = isPasswordRecoveryEmailNotFoundError(recoveryError)
? emailNotFoundMessage
: null;
const initialEmail = initialValues ? initialValues.email : null;
const emailTouched = values.email !== this.submittedValues.email;
const loginLink = (
<NamedLink name="LoginPage" className={css.modalHelperLink}>
<FormattedMessage id="PasswordRecoveryForm.loginLinkText" />
</NamedLink>
);
const classes = classNames(rootClassName || css.root, className);
const submitInProgress = inProgress;
const submittedOnce = Object.keys(this.submittedValues).length > 0;
const pristineSinceLastSubmit = submittedOnce && isEqual(values, this.submittedValues);
const submitDisabled =
(pristine && !initialEmail) || submitInProgress || pristineSinceLastSubmit;
return (
<Form className={classes} onSubmit={handleSubmit}>
<FieldTextInput
className={css.email}
type="email"
id={formId ? `${formId}.email` : 'email'}
name="email"
autoComplete="email"
label={emailLabel}
placeholder={emailPlaceholder}
validate={validators.composeValidators(emailRequired, emailValid)}
customErrorText={customErrorText}
/>
const loginLink = (
<NamedLink name="LoginPage" className={css.modalHelperLink}>
<FormattedMessage id="PasswordRecoveryForm.loginLinkText" />
</NamedLink>
);
<div className={css.bottomWrapper}>
<p className={css.bottomWrapperText}>
<span className={css.modalHelperText}>
<FormattedMessage id="PasswordRecoveryForm.loginLinkInfo" values={{ loginLink }} />
</span>
</p>
return (
<Form
className={classes}
onSubmit={e => {
this.submittedValues = values;
handleSubmit(e);
}}
>
<FieldTextInput
className={css.email}
type="email"
id={formId ? `${formId}.email` : 'email'}
name="email"
autoComplete="email"
label={emailLabel}
placeholder={emailPlaceholder}
validate={validators.composeValidators(emailRequired, emailValid)}
customErrorText={emailTouched ? null : customErrorText}
/>
<PrimaryButton type="submit" inProgress={submitInProgress} disabled={submitDisabled}>
<FormattedMessage id="PasswordRecoveryForm.sendInstructions" />
</PrimaryButton>
</div>
</Form>
);
}}
/>
);
<div className={css.bottomWrapper}>
<p className={css.bottomWrapperText}>
<span className={css.modalHelperText}>
<FormattedMessage
id="PasswordRecoveryForm.loginLinkInfo"
values={{ loginLink }}
/>
</span>
</p>
<PrimaryButton
type="submit"
inProgress={submitInProgress}
disabled={submitDisabled}
>
<FormattedMessage id="PasswordRecoveryForm.sendInstructions" />
</PrimaryButton>
</div>
</Form>
);
}}
/>
);
}
}
PasswordRecoveryFormComponent.defaultProps = {
rootClassName: null,

View file

@ -3193,8 +3193,8 @@ final-form-arrays@^1.0.4:
resolved "https://registry.yarnpkg.com/final-form-arrays/-/final-form-arrays-1.0.4.tgz#74330cb4d64ce07bb1c4a9084d0b65afdac6bf72"
final-form@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.4.0.tgz#3eaa1305d7b03d1e5698ace5ff361d4d20d44ac0"
version "4.5.2"
resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.5.2.tgz#f8a9518784bdc2f72e71548a42b04c640963ca49"
finalhandler@1.1.1:
version "1.1.1"
@ -6889,8 +6889,8 @@ react-final-form-arrays@^1.0.4:
resolved "https://registry.yarnpkg.com/react-final-form-arrays/-/react-final-form-arrays-1.0.4.tgz#888b9b81a806844cf66d73f4e8b60e5f18fa2a11"
react-final-form@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-3.1.5.tgz#6fa0e4c61e49b691b679ef4c8a5ad3fe92df0956"
version "3.3.1"
resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-3.3.1.tgz#dbe40eec853a9c451e5a8deb3b02d948a2fb7e3c"
react-google-maps@^9.4.5:
version "9.4.5"