diff --git a/src/components/inputs/money-input/money-input.form.story.js b/src/components/inputs/money-input/money-input.form.story.js index ed0c015d49..98574afb40 100644 --- a/src/components/inputs/money-input/money-input.form.story.js +++ b/src/components/inputs/money-input/money-input.form.story.js @@ -33,20 +33,15 @@ storiesOf('Examples|Forms/Inputs', module)
{ + onSubmit={(values, formik) => { // eslint-disable-next-line no-console console.log( 'money value', MoneyInput.convertToMoneyValue(values.price) ); - action('onSubmit')(values, formik, ...rest); + action('onSubmit')(values, formik); formik.resetForm(values); }} render={formik => ( @@ -58,6 +53,11 @@ storiesOf('Examples|Forms/Inputs', module) value={formik.values.price} onChange={formik.handleChange} onBlur={formik.handleBlur} + hasCurrencyError={Boolean( + formik.touched.price && + formik.touched.price.currencyCode && + formik.errors.price + )} hasAmountError={Boolean( formik.touched.price && formik.touched.price.amount && diff --git a/src/components/inputs/money-input/money-input.js b/src/components/inputs/money-input/money-input.js index 49103e5038..c02c3a0cde 100644 --- a/src/components/inputs/money-input/money-input.js +++ b/src/components/inputs/money-input/money-input.js @@ -279,7 +279,17 @@ export default class MoneyInput extends React.Component { : formattedAmount, }, }; - this.props.onChange(fakeEvent); + // There is a bug in Formik at the moment where the validation will run + // with wrong values when "handleChange" is called consecutively before + // Formik gets a chance to rerun. + // PR with fix is open: https://github.com/jaredpalmer/formik/pull/939 + // Once merged, we can remove the setTimeout call and call + // onChange directly. + // While the setTimeout workaround is in place, the error messages + // will flicker (appear and disappear) for a split-second. + setTimeout(() => { + this.props.onChange(fakeEvent); + }, 0); } } toggleMenu(); diff --git a/src/components/inputs/money-input/money-input.story.js b/src/components/inputs/money-input/money-input.story.js index aa0e50aa59..044cc2daef 100644 --- a/src/components/inputs/money-input/money-input.story.js +++ b/src/components/inputs/money-input/money-input.story.js @@ -3,70 +3,76 @@ import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, boolean, text, select } from '@storybook/addon-knobs'; import withReadme from 'storybook-readme/with-readme'; -import { Value } from 'react-value'; import Section from '../../../../.storybook/decorators/section'; import MoneyInputReadme from './README.md'; import MoneyInput from './money-input'; -storiesOf('Inputs', module) - .addDecorator(withKnobs) - .addDecorator(withReadme(MoneyInputReadme)) - .add('MoneyInput', () => { +// This uses a dedicated story component to keep track of state instead of +// react-value. The reason is that MoneyInput can call twice onChange before +// the component rerenders, so we'd need to use two separate +// components to not lose data. So we use a dedicated component instead. +// That makes it easier to log the parsed value as well. +class MoneyInputStory extends React.Component { + static displayName = 'MoneyInputStory'; + + state = { + amount: '', + currencyCode: '', + }; + + componentDidUpdate(prevState) { + if ( + prevState.amount !== this.state.amount || + prevState.currencyCode !== this.state.currencyCode + ) { + // eslint-disable-next-line no-console + console.log( + 'parsed', + MoneyInput.convertToMoneyValue({ + amount: this.state.amount, + currencyCode: this.state.currencyCode, + }) + ); + } + } + + render() { const currencies = ['EUR', 'USD', 'AED', 'KWD']; - const defaultCurrencyCode = select( - 'default value currencyCode', - ['', ...currencies], - '' - ); - const defaultAmount = text('default value amount', ''); const name = text('name', '') || 'default-name'; + const value = { + amount: this.state.amount, + currencyCode: this.state.currencyCode, + }; return (
- ( - { - action('onChange')(event); - - const nextMoney = do { - if (event.target.name.endsWith('.amount')) { - ({ ...value, amount: event.target.value }); - } else if (event.target.name.endsWith('.currencyCode')) { - ({ ...value, currencyCode: event.target.value }); - } - }; + { + action('onChange')(event); - onChange(nextMoney); + if (event.target.name.endsWith('.amount')) { + this.setState({ amount: event.target.value }); + } - // eslint-disable-next-line no-console - console.log( - 'parsed', - MoneyInput.convertToMoneyValue(nextMoney) - ); - }} - hasCurrencyError={boolean('hasCurrencyError', false)} - hasCurrencyWarning={boolean('hasCurrencyWarning', false)} - hasAmountError={boolean('hasAmountError', false)} - hasAmountWarning={boolean('hasAmountWarning', false)} - horizontalConstraint={select( - 'horizontalConstraint', - ['s', 'm', 'l', 'xl', 'scale'], - 'm' - )} - /> + if (event.target.name.endsWith('.currencyCode')) { + this.setState({ currencyCode: event.target.value }); + } + }} + hasCurrencyError={boolean('hasCurrencyError', false)} + hasCurrencyWarning={boolean('hasCurrencyWarning', false)} + hasAmountError={boolean('hasAmountError', false)} + hasAmountWarning={boolean('hasAmountWarning', false)} + horizontalConstraint={select( + 'horizontalConstraint', + ['s', 'm', 'l', 'xl', 'scale'], + 'm' )} />
@@ -80,4 +86,10 @@ storiesOf('Inputs', module)
); - }); + } +} + +storiesOf('Inputs', module) + .addDecorator(withKnobs) + .addDecorator(withReadme(MoneyInputReadme)) + .add('MoneyInput', () => );