Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

translating between cents and dollars in html input in React

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 :

  1. 33 in the input box --> becomes 3300 in the parent state --> goes back as 33 in component prop - all good

  2. 33.3 in the input box --> becomes 3330 in the parent state --> goes back as 33.3 in the component prop - all good

  3. 33. in the input box --> becomes 3300 in the parent state --> goes back as 33 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/

like image 435
Michael Avatar asked Jan 21 '15 16:01

Michael


2 Answers

I'm using this simple solution to handle controlled inputs and decimal values.

  1. 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'
        }
    }
    
  2. Set your input value to String one

    <input type="text"
           onChange={this.handleUpdate}
           value={this.state.myPropertyString}/>
    
  3. In handleUpdate method update both state variables.

    handleUpdate(e) {
        this.setState({
            myProperty: parseFloat(e.target.value),
            myPropertyString: e.target.value
        });
    }
    
like image 92
Majky Avatar answered Oct 22 '22 02:10

Majky


Controlled inputs using derived values can be tricksy - if you need to be able to display invalid or otherwise weird input then...

  1. always hold the input's value in its component's own state

    <input value={this.state.value} onChange={this.handleUpdate} // rest as above...
    
  2. derive the initial value in getInitialState()

    getInitialState: function() {
      return {value: this.props.user.balance / 100}
    }
    
  3. implement componentWillReceiveProps(nextProps) to detect when the prop's value is changing from above and re-derive the state value

    componentWillReceiveProps: 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>
like image 36
Jonny Buchanan Avatar answered Oct 22 '22 02:10

Jonny Buchanan