Refactor EditListingPhotosForm: use Form connector from Final Form

This commit is contained in:
Vesa Luusua 2018-04-16 12:24:46 +03:00
parent c38a2bc19b
commit 9c1130b519
2 changed files with 233 additions and 247 deletions

View file

@ -1,51 +1,19 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bool, func, object, shape, string } from 'prop-types';
import { compose } from 'redux';
import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form';
import { Form as FinalForm, Field } from 'react-final-form';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { isEqual } from 'lodash';
import { arrayMove } from 'react-sortable-hoc';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
import { nonEmptyArray } from '../../util/validators';
import { nonEmptyArray, composeValidators } from '../../util/validators';
import { isUploadListingImageOverLimitError } from '../../util/errors';
import { Form, AddImages, Button, ValidationError } from '../../components';
import { AddImages, Button, Form, ValidationError } from '../../components';
import css from './EditListingPhotosForm.css';
const ACCEPT_IMAGES = 'image/*';
// Add image wrapper. Label is the only visible element, file input is hidden.
const RenderAddImage = props => {
const { accept, input, label, type, disabled } = props;
const { name, onChange } = input;
const inputProps = { accept, id: name, name, onChange, type };
return (
<div className={css.addImageWrapper}>
<div className={css.aspectRatioWrapper}>
{disabled ? null : <input {...inputProps} className={css.addImageInput} />}
<label htmlFor={name} className={css.addImage}>
{label}
</label>
</div>
</div>
);
};
const { bool, func, node, object, shape, string } = PropTypes;
RenderAddImage.propTypes = {
accept: string.isRequired,
input: shape({
value: object,
onChange: func.isRequired,
name: string.isRequired,
}).isRequired,
label: node.isRequired,
type: string.isRequired,
disabled: bool.isRequired,
};
export class EditListingPhotosFormComponent extends Component {
constructor(props) {
super(props);
@ -54,14 +22,7 @@ export class EditListingPhotosFormComponent extends Component {
this.onSortEnd = this.onSortEnd.bind(this);
}
componentWillReceiveProps(nextProps) {
if (!isEqual(this.props.images, nextProps.images)) {
nextProps.change('images', nextProps.images);
}
}
onImageUploadHandler(event) {
const file = event.target.files[0];
onImageUploadHandler(file) {
if (file) {
this.setState({ imageUploadRequested: true });
this.props
@ -81,138 +42,176 @@ export class EditListingPhotosFormComponent extends Component {
}
render() {
const {
className,
disabled,
errors,
handleSubmit,
images,
intl,
invalid,
saveActionMsg,
submitting,
updated,
ready,
updateError,
updateInProgress,
onRemoveImage,
} = this.props;
const chooseImageText = (
<span className={css.chooseImageText}>
<span className={css.chooseImage}>
<FormattedMessage id="EditListingPhotosForm.chooseImage" />
</span>
<span className={css.imageTypes}>
<FormattedMessage id="EditListingPhotosForm.imageTypes" />
</span>
</span>
);
const imageRequiredMessage = intl.formatMessage({ id: 'EditListingPhotosForm.imageRequired' });
const { createListingsError, showListingsError, uploadImageError } = errors;
const uploadOverLimit = isUploadListingImageOverLimitError(uploadImageError);
let uploadImageFailed = null;
if (uploadOverLimit) {
uploadImageFailed = (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadOverLimit" />
</p>
);
} else if (uploadImageError) {
uploadImageFailed = (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadFailed" />
</p>
);
}
// NOTE: These error messages are here since Photos panel is the last visible panel
// before creating a new listing. If that order is changed, these should be changed too.
// Create and show listing errors are shown above submit button
const createListingFailed = createListingsError ? (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.createListingFailed" />
</p>
) : null;
const showListingFailed = showListingsError ? (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.showListingFailed" />
</p>
) : null;
// Updating listing (edit mode) failed
const errorMessage = updateError ? (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.updateFailed" />
</p>
) : null;
const classes = classNames(css.root, className);
const submitReady = updated || ready;
const submitInProgress = submitting || updateInProgress;
const submitDisabled =
invalid || disabled || submitInProgress || this.state.imageUploadRequested || ready;
return (
<Form className={classes} onSubmit={handleSubmit}>
{errorMessage}
<AddImages
className={css.imagesField}
images={images}
onSortEnd={this.onSortEnd}
thumbnailClassName={css.thumbnail}
savedImageAltText={intl.formatMessage({ id: 'EditListingPhotosForm.savedImageAltText' })}
onRemoveImage={onRemoveImage}
>
<Field
id="EditListingPhotosForm.AddImages"
accept={ACCEPT_IMAGES}
component={RenderAddImage}
label={chooseImageText}
name="addImage"
onChange={this.onImageUploadHandler}
type="file"
disabled={this.state.imageUploadRequested}
/>
<FinalForm
{...this.props}
onImageUploadHandler={this.onImageUploadHandler}
imageUploadRequested={this.state.imageUploadRequested}
initialValues={{ images: this.props.images }}
render={fieldRenderProps => {
const {
blur,
change,
className,
disabled,
errors,
handleSubmit,
images,
imageUploadRequested,
intl,
invalid,
onImageUploadHandler,
onRemoveImage,
ready,
saveActionMsg,
submitting,
updated,
updateError,
updateInProgress,
} = fieldRenderProps;
<Field
component={props => {
const { input, type, meta } = props;
return (
<div className={css.imageRequiredWrapper}>
<input {...input} type={type} />
<ValidationError fieldMeta={meta} />
</div>
);
}}
name="images"
type="hidden"
validate={[nonEmptyArray(imageRequiredMessage)]}
/>
</AddImages>
{uploadImageFailed}
const chooseImageText = (
<span className={css.chooseImageText}>
<span className={css.chooseImage}>
<FormattedMessage id="EditListingPhotosForm.chooseImage" />
</span>
<span className={css.imageTypes}>
<FormattedMessage id="EditListingPhotosForm.imageTypes" />
</span>
</span>
);
<p className={css.tip}>
<FormattedMessage id="EditListingPhotosForm.addImagesTip" />
</p>
{createListingFailed}
{showListingFailed}
const imageRequiredMessage = intl.formatMessage({
id: 'EditListingPhotosForm.imageRequired',
});
<Button
className={css.submitButton}
type="submit"
inProgress={submitInProgress}
disabled={submitDisabled}
ready={submitReady}
>
{saveActionMsg}
</Button>
</Form>
const { createListingsError, showListingsError, uploadImageError } = errors;
const uploadOverLimit = isUploadListingImageOverLimitError(uploadImageError);
let uploadImageFailed = null;
if (uploadOverLimit) {
uploadImageFailed = (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadOverLimit" />
</p>
);
} else if (uploadImageError) {
uploadImageFailed = (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.imageUploadFailed.uploadFailed" />
</p>
);
}
// NOTE: These error messages are here since Photos panel is the last visible panel
// before creating a new listing. If that order is changed, these should be changed too.
// Create and show listing errors are shown above submit button
const createListingFailed = createListingsError ? (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.createListingFailed" />
</p>
) : null;
const showListingFailed = showListingsError ? (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.showListingFailed" />
</p>
) : null;
const submitReady = updated || ready;
const submitInProgress = submitting || updateInProgress;
const submitDisabled =
invalid || disabled || submitInProgress || imageUploadRequested || ready;
const classes = classNames(css.root, className);
return (
<Form onSubmit={handleSubmit} className={classes}>
{updateError ? (
<p className={css.error}>
<FormattedMessage id="EditListingPhotosForm.updateFailed" />
</p>
) : null}
<AddImages
className={css.imagesField}
images={images}
onSortEnd={this.onSortEnd}
thumbnailClassName={css.thumbnail}
savedImageAltText={intl.formatMessage({
id: 'EditListingPhotosForm.savedImageAltText',
})}
onRemoveImage={onRemoveImage}
>
<Field
id="addImage"
name="addImage"
accept={ACCEPT_IMAGES}
form={null}
label={chooseImageText}
type="file"
disabled={imageUploadRequested}
>
{fieldprops => {
const { accept, input, label, type, disabled } = fieldprops;
const { name } = input;
const onChange = e => {
const file = e.target.files[0];
change(`addImage`, file);
blur(`addImage`);
onImageUploadHandler(file);
};
const inputProps = { accept, id: name, name, onChange, type };
return (
<div className={css.addImageWrapper}>
<div className={css.aspectRatioWrapper}>
{disabled ? null : (
<input {...inputProps} className={css.addImageInput} />
)}
<label htmlFor={name} className={css.addImage}>
{label}
</label>
</div>
</div>
);
}}
</Field>
<Field
component={props => {
const { input, type, meta } = props;
return (
<div className={css.imageRequiredWrapper}>
<input {...input} type={type} />
<ValidationError fieldMeta={meta} />
</div>
);
}}
name="images"
type="hidden"
validate={composeValidators(nonEmptyArray(imageRequiredMessage))}
/>
</AddImages>
{uploadImageFailed}
<p className={css.tip}>
<FormattedMessage id="EditListingPhotosForm.addImagesTip" />
</p>
{createListingFailed}
{showListingFailed}
<Button
className={css.submitButton}
type="submit"
inProgress={submitInProgress}
disabled={submitDisabled}
ready={submitReady}
>
{saveActionMsg}
</Button>
</Form>
);
}}
/>
);
}
}
@ -220,7 +219,6 @@ export class EditListingPhotosFormComponent extends Component {
EditListingPhotosFormComponent.defaultProps = { errors: {}, updateError: null };
EditListingPhotosFormComponent.propTypes = {
...formPropTypes,
errors: shape({
createListingsError: object,
showListingsError: object,
@ -238,6 +236,4 @@ EditListingPhotosFormComponent.propTypes = {
onRemoveImage: func.isRequired,
};
const formName = 'EditListingPhotosForm';
export default compose(reduxForm({ form: formName }), injectIntl)(EditListingPhotosFormComponent);
export default compose(injectIntl)(EditListingPhotosFormComponent);

View file

@ -1,76 +1,66 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditListingPhotosForm matches snapshot 1`] = `
<Form
className=""
contentRef={null}
<ReactFinalForm(4.3.1)(3.1.5)
anyTouched={false}
asyncValidate={[Function]}
asyncValidating={false}
autofill={[Function]}
blur={[Function]}
change={[Function]}
clearAsyncError={[Function]}
clearFields={[Function]}
clearSubmit={[Function]}
clearSubmitErrors={[Function]}
destroy={[Function]}
dirty={false}
dispatch={[Function]}
errors={Object {}}
form="fakeTestForm"
handleSubmit={[Function]}
imageUploadRequested={false}
initialValues={
Object {
"images": undefined,
}
}
initialize={[Function]}
initialized={true}
intl={
Object {
"formatDate": [Function],
"formatHTMLMessage": [Function],
"formatMessage": [Function],
"formatNumber": [Function],
"formatPlural": [Function],
"formatRelative": [Function],
"formatTime": [Function],
"now": [Function],
}
}
invalid={false}
onImageUpload={[Function]}
onImageUploadHandler={[Function]}
onRemoveImage={[Function]}
onSubmit={[Function]}
>
<AddImages
className={null}
images={Array []}
onRemoveImage={[Function]}
onSortEnd={[Function]}
savedImageAltText="EditListingPhotosForm.savedImageAltText"
thumbnailClassName={null}
>
<Field
accept="image/*"
component={[Function]}
disabled={false}
id="EditListingPhotosForm.AddImages"
label={
<span
className={undefined}
>
<span
className={undefined}
>
<FormattedMessage
id="EditListingPhotosForm.chooseImage"
values={Object {}}
/>
</span>
<span
className={undefined}
>
<FormattedMessage
id="EditListingPhotosForm.imageTypes"
values={Object {}}
/>
</span>
</span>
}
name="addImage"
onChange={[Function]}
type="file"
/>
<Field
component={[Function]}
name="images"
type="hidden"
validate={
Array [
[Function],
]
}
/>
</AddImages>
<p>
<FormattedMessage
id="EditListingPhotosForm.addImagesTip"
values={Object {}}
/>
</p>
<Button
className={null}
disabled={false}
inProgress={false}
ready={false}
rootClassName={null}
type="submit"
>
Save photos
</Button>
</Form>
onUpdateImageOrder={[Function]}
pristine={true}
pure={true}
ready={false}
render={[Function]}
reset={[Function]}
resetSection={[Function]}
saveActionMsg="Save photos"
stripeConnected={false}
submit={[Function]}
submitFailed={false}
submitSucceeded={false}
submitting={false}
touch={[Function]}
untouch={[Function]}
updateError={null}
updateInProgress={false}
updated={false}
valid={true}
/>
`;