I'm in a bit of a weird situation, I am dealing with currency in my we app. On the model side, I am saving currency as cents before sending to the server as I don't want to deal with decimal points on the server side. In the view however, I want the to display normal currency and not cents.
So, I have this input field where I take the data from dollars and change it to cents:
<input name="balance" type="number" step="0.01" min="0"
placeholder="Balance in cents" onChange={this.handleUpdate}
value={this.props.user.balance / 100} />
And when there's a change in the input value, I change it back to cents before sending it upstream:
handleUpdate: function(e) {
var value = e.target.value;
// changing it back from cents to dollars
value = parseFloat(value) * 100;
// save back to the parent component managing the prop
this.props.onUserUpdate(value);
}
This puts me in kind of a deadlock, there's no way for me to enter a decimal point "." Let me demonstrate :
33
in the input box --> becomes3300
in the parent state --> goes back as33
in component prop - all good33.3
in the input box --> becomes3330
in the parent state --> goes back as33.3
in the component prop - all good33.
in the input box --> becomes3300
in the parent state --> goes back as33
in the component prop - this is the problem
As you can see in case #3, when the user first enters "." this doesn't translate back to the same number with "."
Since it's a controlled input, there's basically no way of writing "."
I have tried using uncontrolled element with defaultValue
, but the amount prop is not ready the time the component is rendered so it's just empty
/
I'm in a bit of a weird situation, I am dealing with currency in my we app. On the model side, I am saving currency as cents before sending to the server as I don't want to deal with decimal points on the server side. In the view however, I want the to display normal currency and not cents.
So, I have this input field where I take the data from dollars and change it to cents:
<input name="balance" type="number" step="0.01" min="0"
placeholder="Balance in cents" onChange={this.handleUpdate}
value={this.props.user.balance / 100} />
And when there's a change in the input value, I change it back to cents before sending it upstream:
handleUpdate: function(e) {
var value = e.target.value;
// changing it back from cents to dollars
value = parseFloat(value) * 100;
// save back to the parent component managing the prop
this.props.onUserUpdate(value);
}
This puts me in kind of a deadlock, there's no way for me to enter a decimal point "." Let me demonstrate :
33
in the input box --> becomes3300
in the parent state --> goes back as33
in component prop - all good33.3
in the input box --> becomes3330
in the parent state --> goes back as33.3
in the component prop - all good33.
in the input box --> becomes3300
in the parent state --> goes back as33
in the component prop - this is the problem
As you can see in case #3, when the user first enters "." this doesn't translate back to the same number with "."
Since it's a controlled input, there's basically no way of writing "."
I have tried using uncontrolled element with defaultValue
, but the amount prop is not ready the time the component is rendered so it's just empty
http://jsfiddle.net/fpbhu1hs/
Share Improve this question edited Feb 2, 2022 at 17:28 A.L 10.5k10 gold badges71 silver badges105 bronze badges asked Jan 21, 2015 at 16:58 MichaelMichael 23k35 gold badges135 silver badges189 bronze badges2 Answers
Reset to default 13Controlled inputs using derived values can be tricksy - if you need to be able to display invalid or otherwise weird input then...
always hold the input's
value
in its component's ownstate
<input value={this.state.value} onChange={this.handleUpdate} // rest as above...
derive the initial
value
ingetInitialState()
getInitialState: function() { return {value: this.props.user.balance / 100} }
implement
componentWillReceiveProps(nextProps)
to detect when the prop's value is changing from above and re-derive the state valuecomponentWillReceiveProps: function(nextProps) { if (this.props.user.balance != nextProps.user.balance) { this.setState({value: nextProps.user.balance / 100}) } }
Now when the user enters "33.", you store their literal input using setState()
, then call back to the parent.
handleUpdate: function(e) {
var value = e.target.value
this.setState({value: value})
this.props.onUserUpdate(parseFloat(value) * 100)
}
If the value the parent then passes back down to the child via props hasn't changed (3300 == 3300
in this case), then componentWillReceiveProps()
won't do anything.
Working snippet:
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
<div id="example"></div>
<script type="text/jsx;harmony=true">void function() { 'use strict';
var Parent = React.createClass({
getInitialState() {
return {cents: 3300}
},
_changeValue() {
this.setState({cents: Math.round(Math.random() * 2000 + Math.random() * 2000)})
},
_onCentsChange(cents) {
this.setState({cents})
},
render() {
return <div>
<p><strong>Cents:</strong> {this.state.cents.toFixed(0)} <input type="button" onClick={this._changeValue} value="Change"/></p>
<Child cents={this.state.cents} onCentsChange={this._onCentsChange}/>
</div>
}
})
var Child = React.createClass({
getInitialState() {
return {dollars: this.props.cents / 100}
},
componentWillReceiveProps(nextProps) {
if (this.props.cents != nextProps.cents) {
this.setState({dollars: nextProps.cents / 100})
}
},
_onChange(e) {
var dollars = e.target.value
this.setState({dollars})
if (!isNaN(parseFloat(dollars)) && isFinite(dollars)) {
this.props.onCentsChange(parseFloat(dollars) * 100)
}
},
render() {
return <div>
<input type="number" step="0.01" min="0" value={this.state.dollars} onChange={this._onChange}/>
</div>
}
})
React.render(<Parent/>, document.querySelector('#example'))
}()</script>
I'm using this simple solution to handle controlled inputs and decimal values.
Create two props in your state, one to hold actual value and another to hold string.
constructor(props) { .... this.state = { myProperty: 1.42, myPropertyString: '1.42' } }
Set your input value to String one
<input type="text" onChange={this.handleUpdate} value={this.state.myPropertyString}/>
In handleUpdate method update both state variables.
handleUpdate(e) { this.setState({ myProperty: parseFloat(e.target.value), myPropertyString: e.target.value }); }