Refactor BookingDatesForm: use Form connector from Final Form

This commit is contained in:
Vesa Luusua 2018-04-16 12:19:38 +03:00
parent ac003123ef
commit fdeba7369b
2 changed files with 180 additions and 174 deletions

View file

@ -1,12 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { string, bool } from 'prop-types';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm, formValueSelector, propTypes as formPropTypes } from 'redux-form';
import { Form as FinalForm } from 'react-final-form';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import classNames from 'classnames';
import moment from 'moment';
import { required, bookingDatesRequired } from '../../util/validators';
import { required, bookingDatesRequired, composeValidators } from '../../util/validators';
import { START_DATE, END_DATE } from '../../util/dates';
import { propTypes } from '../../util/types';
import config from '../../config';
@ -34,7 +33,7 @@ export class BookingDatesFormComponent extends Component {
// focus on that input, otherwise continue with the
// default handleSubmit function.
handleFormSubmit(e) {
const { startDate, endDate } = this.props.bookingDates;
const { startDate, endDate } = e.bookingDates || {};
if (!startDate) {
e.preventDefault();
this.setState({ focusedInput: START_DATE });
@ -42,28 +41,13 @@ export class BookingDatesFormComponent extends Component {
e.preventDefault();
this.setState({ focusedInput: END_DATE });
} else {
this.props.handleSubmit(e);
this.props.onSubmit(e);
}
}
render() {
const {
rootClassName,
className,
submitButtonWrapperClassName,
unitType,
bookingDates,
form,
price: unitPrice,
intl,
startDatePlaceholder,
endDatePlaceholder,
isOwnListing,
} = this.props;
const { startDate, endDate } = bookingDates;
const { rootClassName, className, price: unitPrice, ...rest } = this.props;
const classes = classNames(rootClassName || css.root, className);
const submitButtonClasses = classNames(css.submitButtonWrapper, submitButtonWrapperClassName);
if (!unitPrice) {
return (
@ -84,91 +68,124 @@ export class BookingDatesFormComponent extends Component {
);
}
const bookingStartLabel = intl.formatMessage({ id: 'BookingDatesForm.bookingStartTitle' });
const bookingEndLabel = intl.formatMessage({ id: 'BookingDatesForm.bookingEndTitle' });
const requiredMessage = intl.formatMessage({ id: 'BookingDatesForm.requiredDate' });
const startDateErrorMessage = intl.formatMessage({
id: 'FieldDateRangeInput.invalidStartDate',
});
const endDateErrorMessage = intl.formatMessage({ id: 'FieldDateRangeInput.invalidEndDate' });
// This is the place to collect breakdown estimation data. See the
// EstimatedBreakdownMaybe component to change the calculations
// for customised payment processes.
const bookingData =
startDate && endDate
? {
unitType,
unitPrice,
startDate,
endDate,
// NOTE: If unitType is `line-item/units`, a new picker
// for the quantity should be added to the form.
quantity: 1,
}
: null;
const bookingInfo = bookingData ? (
<div className={css.priceBreakdownContainer}>
<h3 className={css.priceBreakdownTitle}>
<FormattedMessage id="BookingDatesForm.priceBreakdownTitle" />
</h3>
<EstimatedBreakdownMaybe bookingData={bookingData} />
</div>
) : null;
const dateFormatOptions = {
weekday: 'short',
month: 'short',
day: 'numeric',
};
const now = moment();
const today = now.startOf('day').toDate();
const tomorrow = now
.startOf('day')
.add(1, 'days')
.toDate();
const startDatePlaceholderText =
startDatePlaceholder || intl.formatDate(today, dateFormatOptions);
const endDatePlaceholderText =
endDatePlaceholder || intl.formatDate(tomorrow, dateFormatOptions);
return (
<Form className={className} onSubmit={this.handleFormSubmit}>
<FieldDateRangeInput
className={css.bookingDates}
name="bookingDates"
unitType={unitType}
startDateId={`${form}.bookingStartDate`}
startDateLabel={bookingStartLabel}
startDatePlaceholderText={startDatePlaceholderText}
endDateId={`${form}.bookingEndDate`}
endDateLabel={bookingEndLabel}
endDatePlaceholderText={endDatePlaceholderText}
focusedInput={this.state.focusedInput}
onFocusedInputChange={this.onFocusedInputChange}
format={null}
useMobileMargins
validate={[
required(requiredMessage),
bookingDatesRequired(startDateErrorMessage, endDateErrorMessage),
]}
/>
{bookingInfo}
<p className={css.smallPrint}>
<FormattedMessage
id={
isOwnListing ? 'BookingDatesForm.ownListing' : 'BookingDatesForm.youWontBeChargedInfo'
}
/>
</p>
<div className={submitButtonClasses}>
<PrimaryButton type="submit">
<FormattedMessage id="BookingDatesForm.requestToBook" />
</PrimaryButton>
</div>
</Form>
<FinalForm
{...rest}
unitPrice={unitPrice}
onSubmit={this.handleFormSubmit}
render={fieldRenderProps => {
const {
endDatePlaceholder,
startDatePlaceholder,
form,
handleSubmit,
intl,
isOwnListing,
submitButtonWrapperClassName,
unitPrice,
unitType,
values,
} = fieldRenderProps;
const { startDate, endDate } = values && values.bookingDates ? values.bookingDates : {};
const bookingStartLabel = intl.formatMessage({
id: 'BookingDatesForm.bookingStartTitle',
});
const bookingEndLabel = intl.formatMessage({ id: 'BookingDatesForm.bookingEndTitle' });
const requiredMessage = intl.formatMessage({ id: 'BookingDatesForm.requiredDate' });
const startDateErrorMessage = intl.formatMessage({
id: 'FieldDateRangeInput.invalidStartDate',
});
const endDateErrorMessage = intl.formatMessage({
id: 'FieldDateRangeInput.invalidEndDate',
});
// This is the place to collect breakdown estimation data. See the
// EstimatedBreakdownMaybe component to change the calculations
// for customised payment processes.
const bookingData =
startDate && endDate
? {
unitType,
unitPrice,
startDate,
endDate,
// NOTE: If unitType is `line-item/units`, a new picker
// for the quantity should be added to the form.
quantity: 1,
}
: null;
const bookingInfo = bookingData ? (
<div className={css.priceBreakdownContainer}>
<h3 className={css.priceBreakdownTitle}>
<FormattedMessage id="BookingDatesForm.priceBreakdownTitle" />
</h3>
<EstimatedBreakdownMaybe bookingData={bookingData} />
</div>
) : null;
const dateFormatOptions = {
weekday: 'short',
month: 'short',
day: 'numeric',
};
const now = moment();
const today = now.startOf('day').toDate();
const tomorrow = now
.startOf('day')
.add(1, 'days')
.toDate();
const startDatePlaceholderText =
startDatePlaceholder || intl.formatDate(today, dateFormatOptions);
const endDatePlaceholderText =
endDatePlaceholder || intl.formatDate(tomorrow, dateFormatOptions);
const submitButtonClasses = classNames(
css.submitButtonWrapper,
submitButtonWrapperClassName
);
return (
<Form onSubmit={handleSubmit} className={classes}>
<FieldDateRangeInput
className={css.bookingDates}
name="bookingDates"
unitType={unitType}
startDateId={`${form}.bookingStartDate`}
startDateLabel={bookingStartLabel}
startDatePlaceholderText={startDatePlaceholderText}
endDateId={`${form}.bookingEndDate`}
endDateLabel={bookingEndLabel}
endDatePlaceholderText={endDatePlaceholderText}
focusedInput={this.state.focusedInput}
onFocusedInputChange={this.onFocusedInputChange}
format={null}
useMobileMargins
validate={composeValidators(
required(requiredMessage),
bookingDatesRequired(startDateErrorMessage, endDateErrorMessage)
)}
/>
{bookingInfo}
<p className={css.smallPrint}>
<FormattedMessage
id={
isOwnListing
? 'BookingDatesForm.ownListing'
: 'BookingDatesForm.youWontBeChargedInfo'
}
/>
</p>
<div className={submitButtonClasses}>
<PrimaryButton type="submit">
<FormattedMessage id="BookingDatesForm.requestToBook" />
</PrimaryButton>
</div>
</Form>
);
}}
/>
);
}
}
@ -183,11 +200,7 @@ BookingDatesFormComponent.defaultProps = {
endDatePlaceholder: null,
};
const { instanceOf, shape, string, bool } = PropTypes;
BookingDatesFormComponent.propTypes = {
...formPropTypes,
rootClassName: string,
className: string,
submitButtonWrapperClassName: string,
@ -196,12 +209,6 @@ BookingDatesFormComponent.propTypes = {
price: propTypes.money,
isOwnListing: bool,
// from formValueSelector
bookingDates: shape({
startDate: instanceOf(Date),
endDate: instanceOf(Date),
}).isRequired,
// from injectIntl
intl: intlShape.isRequired,
@ -210,21 +217,7 @@ BookingDatesFormComponent.propTypes = {
endDatePlaceholder: string,
};
const formName = 'BookingDates';
// When a field depends on the value of another field, we must connect
// to the store and select the required values to inject to the
// component.
//
// See: http://redux-form.com/6.6.1/examples/selectingFormValues/
const selector = formValueSelector(formName);
const mapStateToProps = state => ({ bookingDates: selector(state, 'bookingDates') || {} });
const BookingDatesForm = compose(
connect(mapStateToProps),
reduxForm({ form: formName }),
injectIntl
)(BookingDatesFormComponent);
const BookingDatesForm = compose(injectIntl)(BookingDatesFormComponent);
BookingDatesForm.displayName = 'BookingDatesForm';
export default BookingDatesForm;

View file

@ -1,48 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`BookingDatesForm matches snapshot without selected dates 1`] = `
<Form
className={null}
contentRef={null}
onSubmit={[Function]}
>
<FieldDateRangeInput
endDateId="fakeTestForm.bookingEndDate"
endDateLabel="BookingDatesForm.bookingEndTitle"
endDatePlaceholderText="tomorrow"
focusedInput={null}
format={null}
name="bookingDates"
onFocusedInputChange={[Function]}
startDateId="fakeTestForm.bookingStartDate"
startDateLabel="BookingDatesForm.bookingStartTitle"
startDatePlaceholderText="today"
unitType="line-item/night"
useMobileMargins={true}
validate={
Array [
[Function],
[Function],
]
<ReactFinalForm(4.3.1)(3.1.5)
anyTouched={false}
asyncValidate={[Function]}
asyncValidating={false}
autofill={[Function]}
blur={[Function]}
bookingDates={Object {}}
change={[Function]}
clearAsyncError={[Function]}
clearFields={[Function]}
clearSubmit={[Function]}
clearSubmitErrors={[Function]}
destroy={[Function]}
dirty={false}
dispatch={[Function]}
endDatePlaceholder="tomorrow"
form="fakeTestForm"
handleSubmit={[Function]}
initialize={[Function]}
initialized={true}
intl={
Object {
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"now": [Function],
}
/>
<p>
<FormattedMessage
id="BookingDatesForm.youWontBeChargedInfo"
values={Object {}}
/>
</p>
<div
className=""
>
<PrimaryButton
type="submit"
>
<FormattedMessage
id="BookingDatesForm.requestToBook"
values={Object {}}
/>
</PrimaryButton>
</div>
</Form>
}
invalid={false}
isOwnListing={false}
onSubmit={[Function]}
pristine={true}
pure={true}
render={[Function]}
reset={[Function]}
resetSection={[Function]}
startDatePlaceholder="today"
submit={[Function]}
submitButtonWrapperClassName={null}
submitFailed={false}
submitSucceeded={false}
submitting={false}
touch={[Function]}
unitPrice={
Money {
"amount": 1099,
"currency": "USD",
}
}
unitType="line-item/night"
untouch={[Function]}
valid={true}
/>
`;