Gère le changement de prop value sur CurrencyInput

Implémentation avec `getDerivedStateFromProps` discutée sur https://github.com/betagouv/syso/pull/551#discussion_r287703126
pull/551/head
Maxime Quandalle 2019-05-27 17:40:38 +02:00
parent 9900e7a3c7
commit e5f63e1d37
No known key found for this signature in database
GPG Key ID: 428641C03D29CA10
3 changed files with 33 additions and 23 deletions

View File

@ -24,8 +24,8 @@ let currencyFormat = language => ({
class CurrencyInput extends Component {
state = {
value: this.props.value || '',
valueHasChanged: false,
value: this.props.value,
initialValue: this.props.value
}
onChange = this.props.debounce
@ -50,6 +50,14 @@ class CurrencyInput extends Component {
this.onChange(event)
}
// See https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#alternative-1-reset-uncontrolled-component-with-an-id-prop
static getDerivedStateFromProps({ value }, { initialValue }) {
if (value !== initialValue) {
return { value, initialValue: value }
}
return null
}
render() {
let forwardedProps = omit(
['onChange', 'defaultValue', 'language', 'className', 'value'],
@ -62,6 +70,9 @@ class CurrencyInput extends Component {
decimalSeparator
} = currencyFormat(this.props.language)
// We display negative numbers iff this was the provided value (but we allow the user to enter them)
const valueHasChanged = this.state.value !== this.state.initialValue
return (
<div
className={classnames(
@ -73,16 +84,17 @@ class CurrencyInput extends Component {
{...forwardedProps}
thousandSeparator={thousandSeparator}
decimalSeparator={decimalSeparator}
allowNegative={!this.state.valueHasChanged}
allowNegative={!valueHasChanged}
className="currencyInput__input"
inputMode="numeric"
onValueChange={({ value }) => {
this.setState({valueHasChanged: true, value})
this.value = value.toString().replace(/^\-/,'')
this.setState({ value })
this.value = value.toString().replace(/^\-/, '')
this.handleNextChange = true
}}
onChange={this.handleChange}
value={this.state.value.toString()
value={(this.state.value || '')
.toString()
.replace('.', decimalSeparator)}
/>
{!isCurrencyPrefixed && <>&nbsp;</>}

View File

@ -64,7 +64,7 @@ describe('CurrencyInput', () => {
it('should not call onChange while the decimal part is being written', () => {
let onChange = spy()
const input = getInput(<CurrencyInput onChange={onChange} />)
const input = getInput(<CurrencyInput value="111" onChange={onChange} />)
input.simulate('change', { target: { value: '111,', focus: () => {} } })
expect(onChange).not.to.have.been.called
})
@ -105,25 +105,23 @@ describe('CurrencyInput', () => {
clock.restore()
})
it('should initialize with value of the storeValue prop', () => {
const input = getInput(<CurrencyInput storeValue={1} />)
expect(input.prop('value')).to.eq(1)
it('should initialize with value of the value prop', () => {
const wrapper = shallow(<CurrencyInput value={1} />)
expect(wrapper.state('value')).to.eq(1)
})
it('should update its value if the storeValue prop changes', () => {
const wrapper = shallow(<CurrencyInput storeValue={1} />)
wrapper.setProps({ storeValue: 2 })
it('should update its value if the value prop changes', () => {
const wrapper = shallow(<CurrencyInput value={1} />)
wrapper.setProps({ value: 2 })
expect(wrapper.state('value')).to.equal(2)
})
it('should not update state if the storeValue is the same as the current input value', () => {
const wrapper = shallow(
<CurrencyInput storeValue={1000} onChange={() => {}} />
)
it('should not call onChange the value is the same as the current input value', () => {
let onChange = spy()
const wrapper = mount(<CurrencyInput value={1000} onChange={() => {}} />)
const input = wrapper.find('input')
input.simulate('change', { target: { value: '2000' } })
const state1 = wrapper.state()
wrapper.setProps({ storeValue: '2000' })
const state2 = wrapper.state()
expect(state1).to.equal(state2)
input.simulate('change', { target: { value: '2000', focus: () => {} } })
wrapper.setProps({ value: '2000' })
expect(onChange).not.to.have.been.called
})
})

View File

@ -7,7 +7,7 @@ import withLanguage from 'Components/utils/withLanguage'
import withSitePaths from 'Components/utils/withSitePaths'
import { encodeRuleName } from 'Engine/rules'
import { compose, isEmpty, isNil, propEq } from 'ramda'
import React, { Component, PureComponent, useRef } from 'react'
import React, { Component, PureComponent } from 'react'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'