diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f2f9a73be..c7c540e12f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,11 @@ way to update this template, but currently, we follow a pattern: --- -## Upcoming version +## v1.0.0 +* [change] Migrate remaining Redux Forms to Final Form. Also now all the form components can be + found in the src/forms folder. Remove redux-form from the dependencies. + [#845](https://github.com/sharetribe/flex-template-web/pull/845) * [fix] Extract and fix missing information reminder modal from Topbar [#846](https://github.com/sharetribe/flex-template-web/pull/846) * [fix] Add missing styles for ModalMissingInformation from Topbar diff --git a/docs/folder-structure.md b/docs/folder-structure.md index 5a7e22f18f..66dfe1723b 100644 --- a/docs/folder-structure.md +++ b/docs/folder-structure.md @@ -79,10 +79,6 @@ this folder contains only page-level components and one common component: Topbar Forms are in their own folder since they are using a special library to handle validations, errors, internal state, etc. -_**NOTE:** Currently, we are migrating to -[🏁 Final Form](https://github.com/final-form/react-final-form) (away from -[Redux Form](http://redux-form.com/))._ - ## server/ * `server/index.js` handles server-side rendering (SSR). Built with [Express](http://expressjs.com) diff --git a/package.json b/package.json index cea7037363..874012c99b 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "react-redux": "^5.0.7", "react-router-dom": "^4.2.2", "redux": "^3.7.2", - "redux-form": "^7.3.0", "redux-thunk": "^2.2.0", "sanitize.css": "^5.0.0", "sharetribe-scripts": "1.1.2", diff --git a/src/components/FieldBirthdayInput/FieldBirthdayInput.js b/src/components/FieldBirthdayInput/FieldBirthdayInput.js index 2510e410ba..aa0709a4c4 100644 --- a/src/components/FieldBirthdayInput/FieldBirthdayInput.js +++ b/src/components/FieldBirthdayInput/FieldBirthdayInput.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - import React, { Component } from 'react'; import { func, instanceOf, object, node, string, bool } from 'prop-types'; import { Field } from 'react-final-form'; diff --git a/src/components/FieldCheckbox/FieldCheckbox.js b/src/components/FieldCheckbox/FieldCheckbox.js index 2084bbde02..5904e5a652 100644 --- a/src/components/FieldCheckbox/FieldCheckbox.js +++ b/src/components/FieldCheckbox/FieldCheckbox.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - import React from 'react'; import { node, string } from 'prop-types'; import classNames from 'classnames'; diff --git a/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js index 5660f45439..b46dbf16c1 100644 --- a/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js +++ b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - /* * Renders a group of checkboxes that can be used to select * multiple values from a set of options. diff --git a/src/components/FieldCurrencyInput/FieldCurrencyInput.js b/src/components/FieldCurrencyInput/FieldCurrencyInput.js index 998227b076..19050c2e4d 100644 --- a/src/components/FieldCurrencyInput/FieldCurrencyInput.js +++ b/src/components/FieldCurrencyInput/FieldCurrencyInput.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - /** * CurrencyInput renders an input field that format it's value according to currency formatting rules * onFocus: renders given value in unformatted manner: "9999,99" diff --git a/src/components/FieldGroupCheckbox/FieldCheckbox.css b/src/components/FieldGroupCheckbox/FieldCheckbox.css deleted file mode 100644 index a295dbf02c..0000000000 --- a/src/components/FieldGroupCheckbox/FieldCheckbox.css +++ /dev/null @@ -1,68 +0,0 @@ -@import '../../marketplace.css'; - -.root { - position: relative; -} - -.input { - position: absolute; - opacity: 0; - height: 0; - width: 0; - - /* Highlight the borders if the checkbox is hovered, focused or checked */ - &:hover + label .box, - &:focus + label .box, - &:checked + label .box { - stroke: var(--marketplaceColor); - } - - /* Display the "check" when checked */ - &:checked + label .checked { - display: inline; - stroke: var(--marketplaceColor); - stroke-width: 1px; - } - - /* Hightlight the text on checked, hover and focus */ - &:focus + label .text, - &:hover + label .text, - &:checked + label .text { - color: var(--matterColorDark); - } -} - -.label { - display: flex; - align-items: center; - padding: 0; -} - -.checkboxWrapper { - /* This should follow line-height */ - height: 32px; - margin-top: -1px; - margin-right: 12px; - align-self: baseline; - - display: inline-flex; - align-items: center; - cursor: pointer; -} - -.checked { - display: none; - fill: var(--marketplaceColor); -} - -.box { - stroke: var(--matterColorAnti); -} - -.text { - @apply --marketplaceListingAttributeFontStyles; - color: var(--matterColor); - margin-top: -1px; - margin-bottom: 1px; - cursor: pointer; -} diff --git a/src/components/FieldGroupCheckbox/FieldCheckbox.js b/src/components/FieldGroupCheckbox/FieldCheckbox.js deleted file mode 100644 index 13a94036ed..0000000000 --- a/src/components/FieldGroupCheckbox/FieldCheckbox.js +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import { any, node, string, object, shape } from 'prop-types'; -import classNames from 'classnames'; -import { Field } from 'redux-form'; -import { ValidationError } from '../../components'; - -import css from './FieldCheckbox.css'; - -const IconCheckbox = props => { - return ( - - - - - - - - - - ); -}; - -IconCheckbox.defaultProps = { className: null }; - -IconCheckbox.propTypes = { className: string }; - -const FieldCheckboxComponent = props => { - const { rootClassName, className, svgClassName, id, label, input, meta, ...rest } = props; - - const classes = classNames(rootClassName || css.root, className); - - const { value, ...inputProps } = input; - const checked = value === true; - - const checkboxProps = { - id, - className: css.input, - type: 'checkbox', - checked, - ...inputProps, - ...rest, - }; - - return ( - - - - - - ); -}; - -FieldCheckboxComponent.defaultProps = { - className: null, - rootClassName: null, - svgClassName: null, - label: null, -}; - -FieldCheckboxComponent.propTypes = { - className: string, - rootClassName: string, - svgClassName: string, - id: string.isRequired, - label: node, - - // redux-form Field params - input: shape({ value: any }).isRequired, - meta: object.isRequired, -}; - -const FieldCheckbox = props => { - return ; -}; - -export default FieldCheckbox; diff --git a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.css b/src/components/FieldGroupCheckbox/FieldGroupCheckbox.css deleted file mode 100644 index 0550ae37c9..0000000000 --- a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.css +++ /dev/null @@ -1,28 +0,0 @@ -@import '../../marketplace.css'; - -.root { - margin: 0; - padding: 0; - border: none; -} - -.list { - margin: 0; -} - -.twoColumns { - @media (--viewportMedium) { - columns: 2; - } -} - -.item { - padding: 2px 0; - - /* Fix broken multi-column layout in Chrome */ - page-break-inside: avoid; - - @media (--viewportMedium) { - padding: 4px 0; - } -} diff --git a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.example.js b/src/components/FieldGroupCheckbox/FieldGroupCheckbox.example.js deleted file mode 100644 index 54c43adcd9..0000000000 --- a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.example.js +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import { reduxForm, propTypes as formPropTypes } from 'redux-form'; -import { Button } from '../../components'; -import FieldGroupCheckbox from './FieldGroupCheckbox'; -import { requiredFieldArrayCheckbox } from '../../util/validators'; - -const formName = 'Styleguide.FieldGroupCheckboxForm'; -const formNameRequired = 'Styleguide.FieldGroupCheckboxFormRequired'; - -const label =

Amenities

; - -const commonProps = { - label: label, - options: [ - { - key: 'towels', - label: 'Towels', - }, - { - key: 'bathroom', - label: 'Bathroom', - }, - { - key: 'swimming_pool', - label: 'Swimming pool', - }, - { - key: 'own_drinks', - label: 'Own drinks allowed', - }, - { - key: 'jacuzzi', - label: 'Jacuzzi', - }, - { - key: 'audiovisual_entertainment', - label: 'Audiovisual entertainment', - }, - { - key: 'barbeque', - label: 'Barbeque', - }, - { - key: 'own_food_allowed', - label: 'Own food allowed', - }, - ], - twoColumns: true, -}; - -const optionalProps = { - name: 'amenities-optional', - id: `${formName}.amenities-optional`, - ...commonProps, -}; - -const requiredProps = { - name: 'amenities-required', - id: `${formNameRequired}.amenities-required`, - ...commonProps, - validate: requiredFieldArrayCheckbox('this is required'), -}; - -const FormComponent = props => { - const { handleSubmit, invalid, submitting, componentProps } = props; - - const submitDisabled = invalid || submitting; - - return ( -
- - - - - ); -}; - -FormComponent.propTypes = formPropTypes; - -const Form = formName => { - return reduxForm({ - form: formName, - })(FormComponent); -}; - -export const Optional = { - component: Form(formName), - props: { - onSubmit: values => { - console.log('Submit values: ', values); - }, - componentProps: optionalProps, - }, - group: 'inputs', -}; - -export const Required = { - component: Form(formNameRequired), - props: { - onSubmit: values => { - console.log('Submit values: ', values); - }, - componentProps: requiredProps, - }, - group: 'inputs', -}; diff --git a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js b/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js deleted file mode 100644 index 14876e968d..0000000000 --- a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Renders a group of checkboxes that can be used to select - * multiple values from a set of options. - * - * The corresponding component when rendering the selected - * values is PropertyGroup. - * - */ - -import React, { Component } from 'react'; -import { arrayOf, bool, node, shape, string } from 'prop-types'; -import classNames from 'classnames'; -import { FieldArray } from 'redux-form'; -import { ValidationError } from '../../components'; - -import FieldCheckbox from './FieldCheckbox'; -import css from './FieldGroupCheckbox.css'; - -class FieldCheckboxRenderer extends Component { - constructor(props) { - super(props); - this.state = { touched: false }; - } - - componentWillReceiveProps(nextProps) { - // FieldArray doesn't have touched prop, so we'll keep track of it - if (!this.state.touched) { - this.setState({ touched: nextProps.meta.dirty }); - } - } - - render() { - const { className, rootClassName, label, twoColumns, id, options, meta, name } = this.props; - - const touched = this.state.touched; - - const classes = classNames(rootClassName || css.root, className); - const listClasses = twoColumns ? classNames(css.list, css.twoColumns) : css.list; - - return ( -
- {label ? {label} : null} -
    - {options.map(option => { - const fieldId = `${id}.${option.key}`; - return ( -
  • - -
  • - ); - })} -
- -
- ); - } -} - -FieldCheckboxRenderer.defaultProps = { - rootClassName: null, - className: null, - label: null, - twoColumns: false, -}; - -FieldCheckboxRenderer.propTypes = { - rootClassName: string, - className: string, - id: string.isRequired, - label: node, - options: arrayOf( - shape({ - key: string.isRequired, - label: node.isRequired, - }) - ).isRequired, - twoColumns: bool, -}; - -// Redux Form: the name of FieldArray must be unique -// https://github.com/erikras/redux-form/issues/2740 -const FieldGroupCheckbox = props => ( - -); - -// Name and component are required fields for FieldArray. -// Component-prop we define in this file, name needs to be passed in -FieldGroupCheckbox.propTypes = { - name: string.isRequired, -}; - -export default FieldGroupCheckbox; diff --git a/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js b/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js index 6a44b4a5bc..f1c127c5bd 100644 --- a/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js +++ b/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js @@ -1,7 +1,3 @@ -/** - * DEPRECATED: this component is part of Redux Form - we are migrating to react-final-form. - */ - /** * A text field with phone number formatting. By default uses formatting * rules defined in the fiFormatter.js file. To change the formatting diff --git a/src/components/FieldReviewRating/FieldReviewRating.js b/src/components/FieldReviewRating/FieldReviewRating.js index 72b4f34a68..25698b1618 100644 --- a/src/components/FieldReviewRating/FieldReviewRating.js +++ b/src/components/FieldReviewRating/FieldReviewRating.js @@ -114,7 +114,7 @@ FieldReviewRatingComponent.propTypes = { // overrides default validation message customErrorText: string, - // Generated by redux-form's Field component + // Generated by final-form's Field component input: shape({ onChange: func.isRequired, }).isRequired, diff --git a/src/components/FieldSelect/FieldSelect.js b/src/components/FieldSelect/FieldSelect.js index dd13589d7a..d480cd6e41 100644 --- a/src/components/FieldSelect/FieldSelect.js +++ b/src/components/FieldSelect/FieldSelect.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - import React from 'react'; import PropTypes from 'prop-types'; import { Field } from 'react-final-form'; diff --git a/src/components/FieldTextInput/FieldTextInput.js b/src/components/FieldTextInput/FieldTextInput.js index 9af3262c9c..878738ee6c 100644 --- a/src/components/FieldTextInput/FieldTextInput.js +++ b/src/components/FieldTextInput/FieldTextInput.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - import React, { Component } from 'react'; import { func, object, shape, string } from 'prop-types'; import { Field } from 'react-final-form'; @@ -96,7 +92,7 @@ FieldTextInputComponent.propTypes = { // Either 'textarea' or something that is passed to the input element type: string.isRequired, - // Generated by redux-form's Field component + // Generated by final-form's Field component input: shape({ onChange: func.isRequired, }).isRequired, diff --git a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.example.js b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.example.js index 01f9a53a92..183dd688ba 100644 --- a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.example.js +++ b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.example.js @@ -1,30 +1,28 @@ import React, { Component } from 'react'; -import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form'; +import { Form as FinalForm, Field } from 'react-final-form'; import { propTypes } from '../../util/types'; import { Button } from '../../components'; import LocationAutocompleteInput from './LocationAutocompleteInput'; -const FormComponent = props => { - const { handleSubmit, pristine, submitting } = props; +const Form = props => { return ( -
- - - - + { + return ( +
+ + + + + ); + }} + /> ); }; -FormComponent.propTypes = formPropTypes; - -const defaultFormName = 'Styleguide.LocationAutocompleteInput.Form'; - -const Form = reduxForm({ - form: defaultFormName, -})(FormComponent); - const PlaceInfo = props => { const { place } = props; const { address, country, origin, bounds } = place; diff --git a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js index 78c2d54cfe..a501ed136c 100644 --- a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js +++ b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js @@ -1,5 +1,5 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import { any, arrayOf, bool, func, number, object, shape, string, oneOfType } from 'prop-types'; import { Field } from 'react-final-form'; import debounce from 'lodash/debounce'; import classNames from 'classnames'; @@ -93,8 +93,6 @@ const LocationPredictionsList = props => { ); }; -const { any, arrayOf, bool, func, number, object, shape, string } = PropTypes; - LocationPredictionsList.defaultProps = { rootClassName: null, className: null, @@ -128,14 +126,13 @@ const currentValue = props => { /* Location auto completion input component - This component can work as the `component` prop to Redux Form's - component. it takes a custom input value shape, and - controls the onChanged callback that is called with the value to - syncronise to the form's Redux store. + This component can work as the `component` prop to Final Form's + component. It takes a custom input value shape, and + controls the onChange callback that is called with the input value. The component works by listening to the underlying input component and calling the Google Maps Places API for predictions. When the - predictions arrive, those are passed to Redux Form in the onChange + predictions arrive, those are passed to Final Form in the onChange callback. See the LocationAutocompleteInput.example.js file for a usage @@ -475,11 +472,14 @@ LocationAutocompleteInput.propTypes = { placeholder: string, input: shape({ name: string.isRequired, - value: shape({ - search: string, - predictions: any, - selectedPlace: propTypes.place, - }), + value: oneOfType([ + shape({ + search: string, + predictions: any, + selectedPlace: propTypes.place, + }), + string, + ]), onChange: func.isRequired, onFocus: func.isRequired, onBlur: func.isRequired, diff --git a/src/components/PropertyGroup/PropertyGroup.js b/src/components/PropertyGroup/PropertyGroup.js index 0c5356351a..15bb254296 100644 --- a/src/components/PropertyGroup/PropertyGroup.js +++ b/src/components/PropertyGroup/PropertyGroup.js @@ -2,7 +2,7 @@ * Renders a set of options with selected and non-selected values. * * The corresponding component when selecting the values is - * FieldGroupCheckbox. + * FieldCheckboxGroup. * */ diff --git a/src/components/SelectField/SelectField.css b/src/components/SelectField/SelectField.css deleted file mode 100644 index 0aa58c74ce..0000000000 --- a/src/components/SelectField/SelectField.css +++ /dev/null @@ -1,18 +0,0 @@ -@import '../../marketplace.css'; - -.root { -} - -.select { - color: var(--matterColorAnti); - border-bottom-color: var(--attentionColor); -} - -.selectSuccess { - color: var(--matterColor); - border-bottom-color: var(--successColor); -} - -.selectError { - border-bottom-color: var(--failColor); -} diff --git a/src/components/SelectField/SelectField.example.js b/src/components/SelectField/SelectField.example.js deleted file mode 100644 index a348c25d25..0000000000 --- a/src/components/SelectField/SelectField.example.js +++ /dev/null @@ -1,47 +0,0 @@ -/* eslint-disable no-console */ -import React from 'react'; -import { reduxForm, propTypes as formPropTypes } from 'redux-form'; -import * as validators from '../../util/validators'; -import { Button } from '../../components'; -import SelectField from './SelectField'; - -const formName = 'Styleguide.SelectField.Form'; - -const FormComponent = props => { - const { form, handleSubmit, invalid, pristine, submitting } = props; - const required = validators.required('This field is required'); - const submitDisabled = invalid || pristine || submitting; - return ( -
- - - - - - -
- ); -}; - -FormComponent.propTypes = formPropTypes; - -const Form = reduxForm({ - form: formName, -})(FormComponent); - -export const Select = { - component: Form, - props: { - onSubmit: values => { - console.log('submit values:', values); - }, - }, - group: 'inputs', -}; diff --git a/src/components/SelectField/SelectField.js b/src/components/SelectField/SelectField.js deleted file mode 100644 index c733916cba..0000000000 --- a/src/components/SelectField/SelectField.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * DEPRECATED: this component is part of Redux Form - we are migrating to react-final-form. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { Field } from 'redux-form'; -import classNames from 'classnames'; -import { ValidationError } from '../../components'; - -import css from './SelectField.css'; - -const SelectFieldComponent = props => { - const { rootClassName, className, id, label, input, meta, children, ...rest } = props; - - if (label && !id) { - throw new Error('id required when a label is given'); - } - - const { valid, invalid, touched, error } = meta; - - // Error message and input error styles are only shown if the - // field has been touched and the validation has failed. - const hasError = touched && invalid && error; - - const selectClasses = classNames(css.select, { - [css.selectSuccess]: valid, - [css.selectError]: hasError, - }); - const selectProps = { className: selectClasses, id, ...input, ...rest }; - - const classes = classNames(rootClassName || css.root, className); - return ( -
- {label ? : null} - - -
- ); -}; - -SelectFieldComponent.defaultProps = { - rootClassName: null, - className: null, - id: null, - label: null, - children: null, -}; - -const { string, object, node } = PropTypes; - -SelectFieldComponent.propTypes = { - rootClassName: string, - className: string, - - // Label is optional, but if it is given, an id is also required so - // the label can reference the input in the `for` attribute - id: string, - label: string, - - // Generated by redux-form's Field component - input: object.isRequired, - meta: object.isRequired, - - children: node, -}; - -const SelectField = props => { - return ; -}; - -export default SelectField; diff --git a/src/components/SelectMultipleFilter/SelectMultipleFilter.js b/src/components/SelectMultipleFilter/SelectMultipleFilter.js index 85934121ff..3becf3b9aa 100644 --- a/src/components/SelectMultipleFilter/SelectMultipleFilter.js +++ b/src/components/SelectMultipleFilter/SelectMultipleFilter.js @@ -3,8 +3,7 @@ import { array, arrayOf, func, number, string } from 'prop-types'; import classNames from 'classnames'; import { injectIntl, intlShape } from 'react-intl'; -import { arrayToFormValues, formValuesToArray } from '../../util/data'; -import SelectMultipleFilterForm from './SelectMultipleFilterForm'; +import { SelectMultipleFilterForm } from '../../forms'; import css from './SelectMultipleFilter.css'; const KEY_CODE_ESCAPE = 27; @@ -28,9 +27,9 @@ class SelectMultipleFilter extends Component { handleSubmit(values) { const { name, onSelect, urlParam } = this.props; - const selectedKeys = formValuesToArray(values[name]); + const paramValues = values[name]; this.setState({ isOpen: false }); - onSelect(urlParam, selectedKeys); + onSelect(urlParam, paramValues); } handleClear() { @@ -107,13 +106,9 @@ class SelectMultipleFilter extends Component { const contentStyle = this.positionStyleForContent(); - // turn a list of values into a map that can be passed to - // a redux form - const initialValuesObj = arrayToFormValues(initialValues); - // pass the initial values with the name key so that // they can be passed to the correct field - const namedInitialValues = { [name]: initialValuesObj }; + const namedInitialValues = { [name]: initialValues }; return (
{ - const { - form, - destroy, - reset, - handleSubmit, - name, - onClear, - onCancel, - options, - isOpen, - contentRef, - style, - intl, - } = props; - const classes = classNames(css.root, { [css.isOpen]: isOpen }); - - const handleClear = () => { - // clear the redux form state - destroy(); - onClear(); - }; - - const handleCancel = () => { - // reset the redux form to initialValues - reset(); - onCancel(); - }; - - const clear = intl.formatMessage({ id: 'SelectMultipleFilterForm.clear' }); - const cancel = intl.formatMessage({ id: 'SelectMultipleFilterForm.cancel' }); - const submit = intl.formatMessage({ id: 'SelectMultipleFilterForm.submit' }); - - return ( -
- -
- - - -
- - ); -}; - -SelectMultipleFilterFormComponent.defaultProps = { - contentRef: null, - style: null, -}; - -SelectMultipleFilterFormComponent.propTypes = { - ...formPropTypes, - name: string.isRequired, - onClear: func.isRequired, - onCancel: func.isRequired, - options: arrayOf( - shape({ - key: string.isRequired, - label: node.isRequired, - }) - ).isRequired, - isOpen: bool.isRequired, - contentRef: func, - style: object, - - // form injectIntl - intl: intlShape.isRequired, -}; - -const SelectMultipleFilterForm = compose(reduxForm({}), injectIntl)( - SelectMultipleFilterFormComponent -); - -export default SelectMultipleFilterForm; diff --git a/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.example.js b/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.example.js index 986e843387..d96a6cdc45 100644 --- a/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.example.js +++ b/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.example.js @@ -41,6 +41,7 @@ const options = [ ]; const handleSelect = (urlParam, values, history) => { + console.log(`handle select`, values); const queryParams = values ? `?${stringify({ [urlParam]: values.join(',') })}` : ''; history.push(`${window.location.pathname}${queryParams}`); }; diff --git a/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.js b/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.js index 1e4a47c3ab..22790e8ec6 100644 --- a/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.js +++ b/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.js @@ -3,8 +3,7 @@ import { array, bool, func, string } from 'prop-types'; import classNames from 'classnames'; import { injectIntl, intlShape, FormattedMessage } from 'react-intl'; -import SelectMultipleFilterPlainForm from './SelectMultipleFilterPlainForm'; -import { arrayToFormValues, formValuesToArray } from '../../util/data'; +import { SelectMultipleFilterPlainForm } from '../../forms'; import css from './SelectMultipleFilterPlain.css'; @@ -20,8 +19,8 @@ class SelectMultipleFilterPlainComponent extends Component { handleSelect(values) { const { urlParam, name, onSelect } = this.props; - const selectedKeys = formValuesToArray(values[name]); - onSelect(urlParam, selectedKeys); + const paramValues = values[name]; + onSelect(urlParam, paramValues); } handleClear() { @@ -64,8 +63,7 @@ class SelectMultipleFilterPlainComponent extends Component { [css.columnLayout]: twoColumns, }); - const initialValuesObj = arrayToFormValues(initialValues); - const namedInitialValues = { [name]: initialValuesObj }; + const namedInitialValues = { [name]: initialValues }; return (
diff --git a/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlainForm.js b/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlainForm.js deleted file mode 100644 index f2775bbb61..0000000000 --- a/src/components/SelectMultipleFilterPlain/SelectMultipleFilterPlainForm.js +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import { arrayOf, bool, node, shape, string } from 'prop-types'; -import { reduxForm, propTypes as formPropTypes } from 'redux-form'; - -import { FieldGroupCheckbox, Form } from '../../components'; - -const SelectMultipleFilterPlainFormComponent = props => { - const { form, className, name, options, twoColumns } = props; - - return ( -
- - - ); -}; - -SelectMultipleFilterPlainFormComponent.defaultProps = { - className: null, - twoColumns: false, -}; - -SelectMultipleFilterPlainFormComponent.propTypes = { - ...formPropTypes, - className: string, - name: string.isRequired, - options: arrayOf( - shape({ - key: string.isRequired, - label: node.isRequired, - }) - ).isRequired, - twoColumns: bool, -}; - -const SelectMultipleFilterPlainForm = reduxForm({})(SelectMultipleFilterPlainFormComponent); - -export default SelectMultipleFilterPlainForm; diff --git a/src/components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.js b/src/components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.js index 43ec5197ed..3046759c55 100644 --- a/src/components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.js +++ b/src/components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.js @@ -1,7 +1,3 @@ -/** - * NOTE this component is part of react-final-form instead of Redux Form. - */ - /* eslint-disable no-underscore-dangle */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; @@ -23,7 +19,7 @@ import { import StripeBankAccountRequiredInput from './StripeBankAccountRequiredInput'; import css from './StripeBankAccountTokenInputField.css'; -// Since redux-form tracks the onBlur event for marking the field as +// Since final-form tracks the onBlur event for marking the field as // touched (which triggers possible error validation rendering), only // trigger the event asynchronously when no other input within this // component has received focus. diff --git a/src/components/TextInputField/TextInputField.css b/src/components/TextInputField/TextInputField.css deleted file mode 100644 index 2294a88d4d..0000000000 --- a/src/components/TextInputField/TextInputField.css +++ /dev/null @@ -1,19 +0,0 @@ -@import '../../marketplace.css'; - -.root { -} - -.input { - border-bottom-color: var(--attentionColor); -} - -.inputSuccess { - border-bottom-color: var(--successColor); -} - -.inputError { - border-bottom-color: var(--failColor); -} - -.textarea { -} diff --git a/src/components/TextInputField/TextInputField.example.css b/src/components/TextInputField/TextInputField.example.css deleted file mode 100644 index 9fe2d13d28..0000000000 --- a/src/components/TextInputField/TextInputField.example.css +++ /dev/null @@ -1,7 +0,0 @@ -.field { - margin-top: 24px; -} - -.submit { - margin-top: 24px; -} diff --git a/src/components/TextInputField/TextInputField.example.js b/src/components/TextInputField/TextInputField.example.js deleted file mode 100644 index 4007f50d41..0000000000 --- a/src/components/TextInputField/TextInputField.example.js +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint-disable no-console */ -import React from 'react'; -import { reduxForm, propTypes as formPropTypes } from 'redux-form'; -import * as validators from '../../util/validators'; -import { Button } from '../../components'; -import TextInputField from './TextInputField'; - -import css from './TextInputField.example.css'; - -const formName = 'Styleguide.TextInputField.Form'; - -const FormComponent = props => { - const { handleSubmit, invalid, pristine, submitting } = props; - const required = validators.required('This field is required'); - const submitDisabled = invalid || pristine || submitting; - return ( -
- - - - - - - - - ); -}; - -FormComponent.propTypes = formPropTypes; - -const Form = reduxForm({ - form: formName, -})(FormComponent); - -export const Inputs = { - component: Form, - props: { - onSubmit: values => { - console.log('submit values:', values); - }, - }, - group: 'inputs', -}; diff --git a/src/components/TextInputField/TextInputField.js b/src/components/TextInputField/TextInputField.js deleted file mode 100644 index 0014a58cef..0000000000 --- a/src/components/TextInputField/TextInputField.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * DEPRECATED: this component is part of Redux Form - we are migrating to react-final-form. - */ - -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Field } from 'redux-form'; -import classNames from 'classnames'; -import { ValidationError, ExpandingTextarea } from '../../components'; - -import css from './TextInputField.css'; - -const CONTENT_MAX_LENGTH = 5000; - -class TextInputFieldComponent extends Component { - componentWillUnmount() { - if (this.props.clearOnUnmount) { - this.props.input.onChange(''); - } - } - render() { - /* eslint-disable no-unused-vars */ - const { - rootClassName, - className, - inputRootClass, - clearOnUnmount, - customErrorText, - id, - label, - type, - input, - meta, - ...rest - } = this.props; - /* eslint-enable no-unused-vars */ - - if (label && !id) { - throw new Error('id required when a label is given'); - } - - const { valid, invalid, touched, error } = meta; - const isTextarea = type === 'textarea'; - - const errorText = customErrorText || error; - - // 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 fieldMeta = { touched, error: errorText }; - - const inputClasses = - inputRootClass || - classNames(css.input, { - [css.inputSuccess]: valid, - [css.inputError]: hasError, - [css.textarea]: isTextarea, - }); - const inputProps = isTextarea - ? { className: inputClasses, id, rows: 1, maxLength: CONTENT_MAX_LENGTH, ...input, ...rest } - : { className: inputClasses, id, type, ...input, ...rest }; - - const classes = classNames(rootClassName || css.root, className); - return ( -
- {label ? : null} - {isTextarea ? : } - -
- ); - } -} - -TextInputFieldComponent.defaultProps = { - rootClassName: null, - className: null, - inputRootClass: null, - clearOnUnmount: false, - customErrorText: null, - id: null, - label: null, -}; - -const { string, bool, shape, func, object } = PropTypes; - -TextInputFieldComponent.propTypes = { - rootClassName: string, - className: string, - inputRootClass: string, - - clearOnUnmount: bool, - - // Error message that can be manually passed to input field, - // overrides default validation message - customErrorText: string, - - // Label is optional, but if it is given, an id is also required so - // the label can reference the input in the `for` attribute - id: string, - label: string, - - // Either 'textarea' or something that is passed to the input element - type: string.isRequired, - - // Generated by redux-form's Field component - input: shape({ - onChange: func.isRequired, - }).isRequired, - meta: object.isRequired, -}; - -const TextInputField = props => { - return ; -}; - -export default TextInputField; diff --git a/src/components/TransactionPanel/TransactionPanel.js b/src/components/TransactionPanel/TransactionPanel.js index 86e5756c7b..89fefc72c1 100644 --- a/src/components/TransactionPanel/TransactionPanel.js +++ b/src/components/TransactionPanel/TransactionPanel.js @@ -73,9 +73,9 @@ export class TransactionPanelComponent extends Component { this.setState({ sendMessageFormFocused: false }); } - onMessageSubmit(values) { + onMessageSubmit(values, form) { const message = values.message ? values.message.trim() : null; - const { transaction, onResetForm, onSendMessage } = this.props; + const { transaction, onSendMessage } = this.props; const ensuredTransaction = ensureTransaction(transaction); if (!message) { @@ -83,7 +83,7 @@ export class TransactionPanelComponent extends Component { } onSendMessage(ensuredTransaction.id, message) .then(messageId => { - onResetForm(this.sendMessageFormName); + form.reset(); this.scrollToMessage(messageId); }) .catch(e => { @@ -387,7 +387,6 @@ TransactionPanelComponent.propTypes = { onShowMoreMessages: func.isRequired, onSendMessage: func.isRequired, onSendReview: func.isRequired, - onResetForm: func.isRequired, // Sale related props onAcceptSale: func.isRequired, diff --git a/src/components/ValidationError/ValidationError.js b/src/components/ValidationError/ValidationError.js index cfda30b501..b9b3545bce 100644 --- a/src/components/ValidationError/ValidationError.js +++ b/src/components/ValidationError/ValidationError.js @@ -6,7 +6,7 @@ import css from './ValidationError.css'; /** * This component can be used to show validation errors next to form - * input fields. The component takes the redux-form Field component + * input fields. The component takes the final-form Field component * `meta` object as a prop and infers if an error message should be * shown. */ diff --git a/src/components/index.js b/src/components/index.js index 3d5a5b8dcd..5710804942 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -36,7 +36,6 @@ export { default as FieldCheckboxGroup } from './FieldCheckboxGroup/FieldCheckbo export { default as FieldCurrencyInput } from './FieldCurrencyInput/FieldCurrencyInput'; export { default as FieldDateInput } from './FieldDateInput/FieldDateInput'; export { default as FieldDateRangeInput } from './FieldDateRangeInput/FieldDateRangeInput'; -export { default as FieldGroupCheckbox } from './FieldGroupCheckbox/FieldGroupCheckbox'; export { default as FieldPhoneNumberInput } from './FieldPhoneNumberInput/FieldPhoneNumberInput'; export { default as FieldReviewRating } from './FieldReviewRating/FieldReviewRating'; export { default as FieldSelect } from './FieldSelect/FieldSelect'; @@ -115,7 +114,6 @@ export { default as SectionHero } from './SectionHero/SectionHero'; export { default as SectionHowItWorks } from './SectionHowItWorks/SectionHowItWorks'; export { default as SectionLocations } from './SectionLocations/SectionLocations'; export { default as SectionThumbnailLinks } from './SectionThumbnailLinks/SectionThumbnailLinks'; -export { default as SelectField } from './SelectField/SelectField'; export { default as SelectMultipleFilter } from './SelectMultipleFilter/SelectMultipleFilter'; export { default as SelectMultipleFilterPlain, @@ -131,7 +129,6 @@ export { default as TabNav } from './TabNav/TabNav'; export { LinkTabNavHorizontal, ButtonTabNavHorizontal } from './TabNavHorizontal/TabNavHorizontal'; export { default as Tabs } from './Tabs/Tabs'; export { default as TermsOfService } from './TermsOfService/TermsOfService'; -export { default as TextInputField } from './TextInputField/TextInputField'; export { default as Topbar } from './Topbar/Topbar'; export { default as TopbarDesktop } from './TopbarDesktop/TopbarDesktop'; export { default as TopbarMobileMenu } from './TopbarMobileMenu/TopbarMobileMenu'; diff --git a/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap b/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap index 764c0e66fc..7e38d3ebb0 100644 --- a/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap +++ b/src/containers/NotFoundPage/__snapshots__/NotFoundPage.test.js.snap @@ -36,7 +36,7 @@ exports[`NotFoundPageComponent matches snapshot 1`] = ` values={Object {}} />

-
diff --git a/src/containers/TransactionPage/TransactionPage.js b/src/containers/TransactionPage/TransactionPage.js index b3014723a2..02cc4a4fd6 100644 --- a/src/containers/TransactionPage/TransactionPage.js +++ b/src/containers/TransactionPage/TransactionPage.js @@ -4,7 +4,6 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import classNames from 'classnames'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; -import { reset as resetForm } from 'redux-form'; import { propTypes } from '../../util/types'; import { ensureListing, ensureTransaction } from '../../util/data'; import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck'; @@ -48,7 +47,6 @@ export const TransactionPageComponent = props => { intl, messages, onManageDisableScrolling, - onResetForm, onSendMessage, onSendReview, onShowMoreMessages, @@ -153,7 +151,6 @@ export const TransactionPageComponent = props => { onShowMoreMessages={onShowMoreMessages} onSendMessage={onSendMessage} onSendReview={onSendReview} - onResetForm={onResetForm} transactionRole={transactionRole} onAcceptSale={onAcceptSale} onDeclineSale={onDeclineSale} @@ -221,7 +218,6 @@ TransactionPageComponent.propTypes = { sendMessageError: propTypes.error, onShowMoreMessages: func.isRequired, onSendMessage: func.isRequired, - onResetForm: func.isRequired, // from injectIntl intl: intlShape.isRequired, @@ -279,7 +275,6 @@ const mapDispatchToProps = dispatch => { onDeclineSale: transactionId => dispatch(declineSale(transactionId)), onShowMoreMessages: txId => dispatch(fetchMoreMessages(txId)), onSendMessage: (txId, message) => dispatch(sendMessage(txId, message)), - onResetForm: formName => dispatch(resetForm(formName)), onManageDisableScrolling: (componentId, disableScrolling) => dispatch(manageDisableScrolling(componentId, disableScrolling)), onSendReview: (role, tx, reviewRating, reviewContent) => diff --git a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap index 8c2fb326fe..c969f6a5c1 100644 --- a/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap +++ b/src/containers/TransactionPage/__snapshots__/TransactionPage.test.js.snap @@ -53,7 +53,6 @@ exports[`TransactionPage - Order matches snapshot 1`] = ` oldestMessagePageFetched={0} onAcceptSale={[Function]} onDeclineSale={[Function]} - onResetForm={[Function]} onSendMessage={[Function]} onShowMoreMessages={[Function]} sendMessageError={null} @@ -250,7 +249,6 @@ exports[`TransactionPage - Sale matches snapshot 1`] = ` oldestMessagePageFetched={0} onAcceptSale={[Function]} onDeclineSale={[Function]} - onResetForm={[Function]} onSendMessage={[Function]} onShowMoreMessages={[Function]} sendMessageError={null} diff --git a/src/ducks/index.js b/src/ducks/index.js index 0f451fe7e9..9987e5c137 100644 --- a/src/ducks/index.js +++ b/src/ducks/index.js @@ -4,7 +4,6 @@ * https://github.com/erikras/ducks-modular-redux */ -import { reducer as form } from 'redux-form'; import Auth from './Auth.duck'; import EmailVerification from './EmailVerification.duck'; import FlashNotification from './FlashNotification.duck'; @@ -21,7 +20,6 @@ export { LocationFilter, Routing, UI, - form, marketplaceData, user, }; diff --git a/src/examples.js b/src/examples.js index 846be7576d..c8c5994e95 100644 --- a/src/examples.js +++ b/src/examples.js @@ -12,7 +12,6 @@ import * as FieldCheckboxGroup from './components/FieldCheckboxGroup/FieldCheckb import * as FieldCurrencyInput from './components/FieldCurrencyInput/FieldCurrencyInput.example'; import * as FieldDateInput from './components/FieldDateInput/FieldDateInput.example'; import * as FieldDateRangeInput from './components/FieldDateRangeInput/FieldDateRangeInput.example'; -import * as FieldGroupCheckbox from './components/FieldGroupCheckbox/FieldGroupCheckbox.example'; import * as FieldPhoneNumberInput from './components/FieldPhoneNumberInput/FieldPhoneNumberInput.example'; import * as FieldReviewRating from './components/FieldReviewRating/FieldReviewRating.example'; import * as FieldSelect from './components/FieldSelect/FieldSelect.example'; @@ -49,14 +48,12 @@ import * as ResponsiveImage from './components/ResponsiveImage/ResponsiveImage.e import * as ReviewRating from './components/ReviewRating/ReviewRating.example'; import * as Reviews from './components/Reviews/Reviews.example'; import * as SectionThumbnailLinks from './components/SectionThumbnailLinks/SectionThumbnailLinks.example'; -import * as SelectField from './components/SelectField/SelectField.example'; import * as SelectMultipleFilter from './components/SelectMultipleFilter/SelectMultipleFilter.example'; import * as SelectMultipleFilterPlain from './components/SelectMultipleFilterPlain/SelectMultipleFilterPlain.example'; import * as StripeBankAccountTokenInputField from './components/StripeBankAccountTokenInputField/StripeBankAccountTokenInputField.example'; import * as TabNav from './components/TabNav/TabNav.example'; import * as TabNavHorizontal from './components/TabNavHorizontal/TabNavHorizontal.example'; import * as Tabs from './components/Tabs/Tabs.example'; -import * as TextInputField from './components/TextInputField/TextInputField.example'; import * as TopbarDesktop from './components/TopbarDesktop/TopbarDesktop.example'; import * as UserCard from './components/UserCard/UserCard.example'; @@ -107,7 +104,6 @@ export { FieldCurrencyInput, FieldDateInput, FieldDateRangeInput, - FieldGroupCheckbox, FieldPhoneNumberInput, FieldReviewRating, FieldSelect, @@ -149,7 +145,6 @@ export { ReviewRating, Reviews, SectionThumbnailLinks, - SelectField, SelectMultipleFilter, SelectMultipleFilterPlain, SendMessageForm, @@ -159,7 +154,6 @@ export { TabNav, TabNavHorizontal, Tabs, - TextInputField, TopbarDesktop, Typography, UserCard, diff --git a/src/forms/EmailVerificationForm/EmailVerificationForm.js b/src/forms/EmailVerificationForm/EmailVerificationForm.js index 0048d3e3ef..41b670fbb1 100644 --- a/src/forms/EmailVerificationForm/EmailVerificationForm.js +++ b/src/forms/EmailVerificationForm/EmailVerificationForm.js @@ -1,8 +1,8 @@ import React from 'react'; -import PropTypes from 'prop-types'; +import { bool } from 'prop-types'; import { compose } from 'redux'; import { FormattedMessage, injectIntl } from 'react-intl'; -import { reduxForm, Field, propTypes as formPropTypes } from 'redux-form'; +import { Form as FinalForm, Field } from 'react-final-form'; import { Form, NamedLink, @@ -14,79 +14,84 @@ import { propTypes } from '../../util/types'; import css from './EmailVerificationForm.css'; -const EmailVerificationFormComponent = props => { - const { currentUser, inProgress, handleSubmit, verificationError } = props; - - const { email, emailVerified, pendingEmail, profile } = currentUser.attributes; - const emailToVerify = {pendingEmail || email}; - const name = profile.firstName; - - const errorMessage = ( -
- -
- ); - - const submitInProgress = inProgress; - const submitDisabled = submitInProgress; - - const verifyEmail = ( -
-
- -

- -

- -

- -

- - {verificationError ? errorMessage : null} -
- -
- - -
- - {inProgress ? ( - - ) : ( - - )} - +const EmailVerificationFormComponent = props => ( + { + const { currentUser, inProgress, handleSubmit, verificationError } = formRenderProps; + + const { email, emailVerified, pendingEmail, profile } = currentUser.attributes; + const emailToVerify = {pendingEmail || email}; + const name = profile.firstName; + + const errorMessage = ( +
+
- -
- ); - - const alreadyVerified = ( -
-
- -

- -

- -

- -

-
- -
- - - -
-
- ); - - return emailVerified && !pendingEmail ? alreadyVerified : verifyEmail; -}; + ); + + const submitInProgress = inProgress; + const submitDisabled = submitInProgress; + + const verifyEmail = ( +
+
+ +

+ +

+ +

+ +

+ + {verificationError ? errorMessage : null} +
+ +
+ + +
+ + {inProgress ? ( + + ) : ( + + )} + +
+ +
+ ); + + const alreadyVerified = ( +
+
+ +

+ +

+ +

+ +

+
+ +
+ + + +
+
+ ); + + return emailVerified && !pendingEmail ? alreadyVerified : verifyEmail; + }} + /> +); EmailVerificationFormComponent.defaultProps = { currentUser: null, @@ -94,19 +99,12 @@ EmailVerificationFormComponent.defaultProps = { verificationError: null, }; -const { bool } = PropTypes; - EmailVerificationFormComponent.propTypes = { - ...formPropTypes, inProgress: bool, currentUser: propTypes.currentUser.isRequired, verificationError: propTypes.error, }; -const defaultFormName = 'EmailVerificationForm'; - -const EmailVerificationForm = compose(reduxForm({ form: defaultFormName }), injectIntl)( - EmailVerificationFormComponent -); +const EmailVerificationForm = compose(injectIntl)(EmailVerificationFormComponent); export default EmailVerificationForm; diff --git a/src/forms/LocationSearchForm/LocationSearchForm.js b/src/forms/LocationSearchForm/LocationSearchForm.js index b703168556..b65704c081 100644 --- a/src/forms/LocationSearchForm/LocationSearchForm.js +++ b/src/forms/LocationSearchForm/LocationSearchForm.js @@ -1,7 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { compose } from 'redux'; -import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form'; +import { func, string } from 'prop-types'; +import { Form as FinalForm, Field } from 'react-final-form'; import { intlShape, injectIntl } from 'react-intl'; import classNames from 'classnames'; import { Form, LocationAutocompleteInput } from '../../components'; @@ -9,47 +8,66 @@ import { Form, LocationAutocompleteInput } from '../../components'; import css from './LocationSearchForm.css'; const LocationSearchFormComponent = props => { - const { rootClassName, className, intl, onSubmit } = props; - - const onChange = location => { + const handleChange = location => { if (location.selectedPlace) { // Note that we use `onSubmit` instead of the conventional // `handleSubmit` prop for submitting. We want to autosubmit // when a place is selected, and don't require any extra // validations for the form. - onSubmit({ location }); + props.onSubmit({ location }); } }; - const classes = classNames(rootClassName || css.root, className); + return ( + { + const { rootClassName, className, intl } = formRenderProps; + const classes = classNames(rootClassName || css.root, className); + + // Allow form submit only when the place has changed + const preventFormSubmit = e => e.preventDefault(); - // Allow form submit only when the place has changed - const preventFormSubmit = e => e.preventDefault(); + return ( +
+ { + const { onChange, ...restInput } = input; - return ( - - - + // Merge the standard onChange function with custom behaviur. A better solution would + // be to use the FormSpy component from Final Form and pass this.onChange to the + // onChange prop but that breaks due to insufficient subscription handling. + // See: https://github.com/final-form/react-final-form/issues/159 + const searchOnChange = value => { + onChange(value); + handleChange(value); + }; + + const searchInput = { ...restInput, onChange: searchOnChange }; + return ( + + ); + }} + /> + + ); + }} + /> ); }; -const { func, string } = PropTypes; - LocationSearchFormComponent.defaultProps = { rootClassName: null, className: null }; LocationSearchFormComponent.propTypes = { - ...formPropTypes, - rootClassName: string, className: string, onSubmit: func.isRequired, @@ -58,10 +76,6 @@ LocationSearchFormComponent.propTypes = { intl: intlShape.isRequired, }; -const defaultFormName = 'TopbarSearchForm'; - -const LocationSearchForm = compose(reduxForm({ form: defaultFormName }), injectIntl)( - LocationSearchFormComponent -); +const LocationSearchForm = injectIntl(LocationSearchFormComponent); export default LocationSearchForm; diff --git a/src/components/SelectMultipleFilter/SelectMultipleFilterForm.css b/src/forms/SelectMultipleFilterForm/SelectMultipleFilterForm.css similarity index 100% rename from src/components/SelectMultipleFilter/SelectMultipleFilterForm.css rename to src/forms/SelectMultipleFilterForm/SelectMultipleFilterForm.css diff --git a/src/forms/SelectMultipleFilterForm/SelectMultipleFilterForm.js b/src/forms/SelectMultipleFilterForm/SelectMultipleFilterForm.js new file mode 100644 index 0000000000..517b5f2159 --- /dev/null +++ b/src/forms/SelectMultipleFilterForm/SelectMultipleFilterForm.js @@ -0,0 +1,97 @@ +import React from 'react'; +import { arrayOf, bool, func, node, object, shape, string } from 'prop-types'; +import classNames from 'classnames'; +import { Form as FinalForm } from 'react-final-form'; +import { injectIntl, intlShape } from 'react-intl'; +import arrayMutators from 'final-form-arrays'; + +import { FieldCheckboxGroup, Form } from '../../components'; +import css from './SelectMultipleFilterForm.css'; + +const SelectMultipleFilterFormComponent = props => { + return ( + { + const { + form, + handleSubmit, + name, + onClear, + onCancel, + options, + isOpen, + contentRef, + style, + intl, + } = formRenderProps; + const classes = classNames(css.root, { [css.isOpen]: isOpen }); + + const handleCancel = () => { + // reset the final form to initialValues + form.reset(); + onCancel(); + }; + + const clear = intl.formatMessage({ id: 'SelectMultipleFilterForm.clear' }); + const cancel = intl.formatMessage({ id: 'SelectMultipleFilterForm.cancel' }); + const submit = intl.formatMessage({ id: 'SelectMultipleFilterForm.submit' }); + return ( +
+ +
+ + + +
+ + ); + }} + /> + ); +}; + +SelectMultipleFilterFormComponent.defaultProps = { + contentRef: null, + style: null, +}; + +SelectMultipleFilterFormComponent.propTypes = { + name: string.isRequired, + onClear: func.isRequired, + onCancel: func.isRequired, + options: arrayOf( + shape({ + key: string.isRequired, + label: node.isRequired, + }) + ).isRequired, + isOpen: bool.isRequired, + contentRef: func, + style: object, + + // form injectIntl + intl: intlShape.isRequired, +}; + +const SelectMultipleFilterForm = injectIntl(SelectMultipleFilterFormComponent); + +export default SelectMultipleFilterForm; diff --git a/src/forms/SelectMultipleFilterPlainForm/SelectMultipleFilterPlainForm.js b/src/forms/SelectMultipleFilterPlainForm/SelectMultipleFilterPlainForm.js new file mode 100644 index 0000000000..6bc44ee018 --- /dev/null +++ b/src/forms/SelectMultipleFilterPlainForm/SelectMultipleFilterPlainForm.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { arrayOf, bool, node, shape, string, func } from 'prop-types'; +import { Form as FinalForm, FormSpy } from 'react-final-form'; +import arrayMutators from 'final-form-arrays'; + +import { FieldCheckboxGroup, Form } from '../../components'; + +const SelectMultipleFilterPlainForm = props => { + const { onChange, ...rest } = props; + + const handleChange = formState => { + if (formState.dirty) { + onChange(formState.values); + } + }; + + return ( + null} + mutators={{ ...arrayMutators }} + render={formRenderProps => { + const { className, name, options, twoColumns } = formRenderProps; + return ( +
+ + + + ); + }} + /> + ); +}; + +SelectMultipleFilterPlainForm.defaultProps = { + className: null, + twoColumns: false, + onChange: () => null, +}; + +SelectMultipleFilterPlainForm.propTypes = { + className: string, + name: string.isRequired, + options: arrayOf( + shape({ + key: string.isRequired, + label: node.isRequired, + }) + ).isRequired, + twoColumns: bool, + onChange: func, +}; + +export default SelectMultipleFilterPlainForm; diff --git a/src/forms/SendMessageForm/SendMessageForm.example.js b/src/forms/SendMessageForm/SendMessageForm.example.js index e1a8cb798a..dc97878519 100644 --- a/src/forms/SendMessageForm/SendMessageForm.example.js +++ b/src/forms/SendMessageForm/SendMessageForm.example.js @@ -3,7 +3,6 @@ import SendMessageForm from './SendMessageForm'; export const Empty = { component: SendMessageForm, props: { - form: 'Styleguide_SendMessageForm_Empty', messagePlaceholder: 'Send message to Juho…', onChange: values => { console.log('values changed to:', values); @@ -24,9 +23,11 @@ export const Empty = { export const InProgress = { component: SendMessageForm, props: { - form: 'Styleguide_SendMessageForm_InProgress', messagePlaceholder: 'Send message to Juho…', inProgress: true, + onSubmit: values => { + console.log('submit values:', values); + }, }, group: 'forms', }; @@ -34,9 +35,11 @@ export const InProgress = { export const Error = { component: SendMessageForm, props: { - form: 'Styleguide_SendMessageForm_Error', messagePlaceholder: 'Send message to Juho…', sendMessageError: { type: 'error', name: 'ExampleError' }, + onSubmit: values => { + console.log('submit values:', values); + }, }, group: 'forms', }; diff --git a/src/forms/SendMessageForm/SendMessageForm.js b/src/forms/SendMessageForm/SendMessageForm.js index 71146dd6bb..c0e55d17e2 100644 --- a/src/forms/SendMessageForm/SendMessageForm.js +++ b/src/forms/SendMessageForm/SendMessageForm.js @@ -2,9 +2,9 @@ import React, { Component } from 'react'; import { string, bool, func } from 'prop-types'; import { compose } from 'redux'; import { FormattedMessage, injectIntl, intlShape } from 'react-intl'; -import { reduxForm, propTypes as formPropTypes } from 'redux-form'; +import { Form as FinalForm } from 'react-final-form'; import classNames from 'classnames'; -import { Form, TextInputField, SecondaryButton } from '../../components'; +import { Form, FieldTextInput, SecondaryButton } from '../../components'; import { propTypes } from '../../util/types'; import css from './SendMessageForm.css'; @@ -36,10 +36,12 @@ class SendMessageFormComponent extends Component { this.handleBlur = this.handleBlur.bind(this); this.blurTimeoutId = null; } + handleFocus() { this.props.onFocus(); window.clearTimeout(this.blurTimeoutId); } + handleBlur() { // We only trigger a blur if another focus event doesn't come // within a timeout. This enables keeping the focus synced when @@ -49,53 +51,60 @@ class SendMessageFormComponent extends Component { this.props.onBlur(); }, BLUR_TIMEOUT_MS); } - render() { - const { - rootClassName, - className, - messagePlaceholder, - form, - handleSubmit, - inProgress, - sendMessageError, - invalid, - } = this.props; - - const classes = classNames(rootClassName || css.root, className); - const submitInProgress = inProgress; - const submitDisabled = invalid || submitInProgress; + render() { return ( -
- -
-
- {sendMessageError ? ( -

- -

- ) : null} -
- - - - -
- + { + const { + rootClassName, + className, + messagePlaceholder, + handleSubmit, + inProgress, + sendMessageError, + invalid, + form, + } = formRenderProps; + + const classes = classNames(rootClassName || css.root, className); + const submitInProgress = inProgress; + const submitDisabled = invalid || submitInProgress; + return ( +
handleSubmit(values, form)}> + +
+
+ {sendMessageError ? ( +

+ +

+ ) : null} +
+ + + + +
+ + ); + }} + /> ); } } @@ -111,12 +120,12 @@ SendMessageFormComponent.defaultProps = { }; SendMessageFormComponent.propTypes = { - ...formPropTypes, rootClassName: string, className: string, inProgress: bool, messagePlaceholder: string, + onSubmit: func.isRequired, onFocus: func, onBlur: func, sendMessageError: propTypes.error, @@ -125,11 +134,7 @@ SendMessageFormComponent.propTypes = { intl: intlShape.isRequired, }; -const defaultFormName = 'SendMessageForm'; - -const SendMessageForm = compose(reduxForm({ form: defaultFormName }), injectIntl)( - SendMessageFormComponent -); +const SendMessageForm = compose(injectIntl)(SendMessageFormComponent); SendMessageForm.displayName = 'SendMessageForm'; diff --git a/src/forms/TopbarSearchForm/TopbarSearchForm.js b/src/forms/TopbarSearchForm/TopbarSearchForm.js index 2127ff82d0..eb15e5afca 100644 --- a/src/forms/TopbarSearchForm/TopbarSearchForm.js +++ b/src/forms/TopbarSearchForm/TopbarSearchForm.js @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import PropTypes 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 { intlShape, injectIntl } from 'react-intl'; import classNames from 'classnames'; import { Form, LocationAutocompleteInput } from '../../components'; @@ -30,33 +29,59 @@ class TopbarSearchFormComponent extends Component { } render() { - const { rootClassName, className, desktopInputRoot, intl, isMobile } = this.props; + return ( + { + const { rootClassName, className, desktopInputRoot, intl, isMobile } = formRenderProps; - const classes = classNames(rootClassName, className); - const desktopInputRootClass = desktopInputRoot || css.desktopInputRoot; + const classes = classNames(rootClassName, className); + const desktopInputRootClass = desktopInputRoot || css.desktopInputRoot; - // Allow form submit only when the place has changed - const preventFormSubmit = e => e.preventDefault(); + // Allow form submit only when the place has changed + const preventFormSubmit = e => e.preventDefault(); - return ( -
- { - this.searchInput = node; - }} - /> - + return ( +
+ { + const { onChange, ...restInput } = input; + + // Merge the standard onChange function with custom behaviur. A better solution would + // be to use the FormSpy component from Final Form and pass this.onChange to the + // onChange prop but that breaks due to insufficient subscription handling. + // See: https://github.com/final-form/react-final-form/issues/159 + const searchOnChange = value => { + onChange(value); + this.onChange(value); + }; + + const searchInput = { ...restInput, onChange: searchOnChange }; + return ( + { + this.searchInput = node; + }} + input={searchInput} + meta={meta} + /> + ); + }} + /> + + ); + }} + /> ); } } @@ -71,8 +96,6 @@ TopbarSearchFormComponent.defaultProps = { }; TopbarSearchFormComponent.propTypes = { - ...formPropTypes, - rootClassName: string, className: string, desktopInputRoot: string, @@ -83,10 +106,6 @@ TopbarSearchFormComponent.propTypes = { intl: intlShape.isRequired, }; -const defaultFormName = 'TopbarSearchForm'; - -const TopbarSearchForm = compose(reduxForm({ form: defaultFormName }), injectIntl)( - TopbarSearchFormComponent -); +const TopbarSearchForm = injectIntl(TopbarSearchFormComponent); export default TopbarSearchForm; diff --git a/src/forms/index.js b/src/forms/index.js index 1d2a9979a8..e25116d724 100644 --- a/src/forms/index.js +++ b/src/forms/index.js @@ -17,6 +17,8 @@ export { default as PayoutDetailsForm } from './PayoutDetailsForm/PayoutDetailsF export { default as ProfileSettingsForm } from './ProfileSettingsForm/ProfileSettingsForm'; export { default as ReviewForm } from './ReviewForm/ReviewForm'; export { default as SendMessageForm } from './SendMessageForm/SendMessageForm'; +export { default as SelectMultipleFilterForm } from './SelectMultipleFilterForm/SelectMultipleFilterForm'; +export { default as SelectMultipleFilterPlainForm } from './SelectMultipleFilterPlainForm/SelectMultipleFilterPlainForm'; export { default as SignupForm } from './SignupForm/SignupForm'; export { default as StripePaymentForm } from './StripePaymentForm/StripePaymentForm'; export { default as TopbarSearchForm } from './TopbarSearchForm/TopbarSearchForm'; diff --git a/src/util/data.js b/src/util/data.js index bca9bd2176..45595c7bf6 100644 --- a/src/util/data.js +++ b/src/util/data.js @@ -1,6 +1,5 @@ import isArray from 'lodash/isArray'; import reduce from 'lodash/reduce'; -import toPairs from 'lodash/toPairs'; /** * Combine the given relationships objects @@ -283,39 +282,3 @@ export const overrideArrays = (objValue, srcValue, key, object, source, stack) = return srcValue; } }; - -/** - * Converts an array of strings into an object where the array items - * are keys and values in all fields are true. This kind of object - * can be passed as initialValues parameter to a ReduxForm that contains - * checkbox inputs. - * - * @param {Array} array An array of strings - * - * @return {Object} An object containing the array items as keys and all - * the values set to true - * - * A complementary function to formValuesToArray. - * - */ -export const arrayToFormValues = array => { - return array.reduce((map, key) => { - map[key] = true; - return map; - }, {}); -}; - -/** - * Converts a values object received form a Redux Form containing - * checkboxes into an array that contains only the values. - * - * @param {Object} formValues A values object received from a Redux Form - * - * @return {Array} An array containing the keys of the formValues parameter - * - * A complementary function to arrayToFormValues. - */ -export const formValuesToArray = formValues => { - const entries = toPairs(formValues); - return entries.filter(entry => entry[1] === true).map(entry => entry[0]); -}; diff --git a/src/util/data.test.js b/src/util/data.test.js index aa9ed9df14..f19e5e7310 100644 --- a/src/util/data.test.js +++ b/src/util/data.test.js @@ -328,38 +328,4 @@ describe('data utils', () => { ]); }); }); - - describe('arrayToFormValues()', () => { - it('converts an empty array', () => { - expect(arrayToFormValues([])).toEqual({}); - }); - it('converts multiple values', () => { - const array = ['one', 'two', 'three']; - const formValues = { one: true, two: true, three: true }; - expect(arrayToFormValues(array)).toEqual(formValues); - }); - }); - - describe('formValuesToArray()', () => { - it('converts an empty object', () => { - expect(formValuesToArray({})).toEqual([]); - }); - it('converts multiple values', () => { - const formValues = { one: true, two: true, three: true }; - const array = ['one', 'two', 'three']; - expect(formValuesToArray(formValues)).toEqual(array); - }); - it('it skips non-true values form input object', () => { - const formValues = { - one: true, - two: null, - three: 'true', - four: false, - five: '', - six: true, - }; - const array = ['one', 'six']; - expect(formValuesToArray(formValues)).toEqual(array); - }); - }); }); diff --git a/src/util/validators.js b/src/util/validators.js index dc91a7ee70..07fe966033 100644 --- a/src/util/validators.js +++ b/src/util/validators.js @@ -12,10 +12,10 @@ const isNonEmptyString = val => { }; /** - * Validator functions and helpers for Redux Forms + * Validator functions and helpers for Final Forms */ -// Redux Form expects and undefined value for a successful validation +// Final Form expects and undefined value for a successful validation const VALID = undefined; export const required = message => value => { diff --git a/yarn.lock b/yarn.lock index 4608ed5041..28d73ec4fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2579,10 +2579,6 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-symbol "~3.1.1" next-tick "1" -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -4001,12 +3997,6 @@ invariant@^2.0.0, invariant@^2.1.1, invariant@^2.2.1, invariant@^2.2.2: dependencies: loose-envify "^1.0.0" -invariant@^2.2.3: - version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" - dependencies: - loose-envify "^1.0.0" - invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" @@ -7072,19 +7062,6 @@ reduce-function-call@^1.0.1, reduce-function-call@^1.0.2: dependencies: balanced-match "^0.4.2" -redux-form@^7.3.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-7.3.0.tgz#b92ef1639c86a6009b0821aacfc80ad8b5ac8c05" - dependencies: - deep-equal "^1.0.1" - es6-error "^4.1.1" - hoist-non-react-statics "^2.5.0" - invariant "^2.2.3" - is-promise "^2.1.0" - lodash "^4.17.5" - lodash-es "^4.17.5" - prop-types "^15.6.1" - redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"