6

I've run into this problem a few times, and I'm looking for the "react way" to solve this.

The Problem

A number field is temporarily invalid while the user is typing. For example: "-45" is "-" and "2.3" is "2.". The simplest validation example is just to parseFloat on the value. Here's a working example: http://jsfiddle.net/h55kruca/6/. I'm looking for a way to validate onBlur instead of onChange, or something similar that achieves this.

Here's the JS in the example:

var App = React.createClass({
    getInitialState: function() {
        return {number: 0}
    },    
    handleNumberChange: function(e) {
        var val = parseFloat(e.target.value);
        this.setState({number: val});
    },
    render: function() {
        return (
            <Editor number={this.state.number}
                    onNumberChange={this.handleNumberChange}
            />
        );
    }
});

var Editor = React.createClass({
    render: function() {
        return (
            <form className="reactForm" >
                <input type='text'
                       value={this.props.number}
                       onChange={this.props.onNumberChange} />
                <span>{this.props.number}</span>
            </form>
        );
    }
});

Note that the validation (parseFloat in this case) is happening in a parent or grandparent (and is ultimately stored on the server) and the Editor's props.number changes by user input or from refreshed data from the server. This is the reason for a controlled component.

Approach #0

I tried using onBlur instead of onChange, and react gave me this warning: Warning: Failed form propType: You provided a 'value' prop to a form field without an 'onChange' handler. This will render a read-only field. If the field should be mutable use 'defaultValue'. Otherwise, set either 'onChange' or 'readOnly'.

Approach #1

Store a temporary "number" state that is actually a string. Use that for whatever the user types in the input until onBlur. I don't like this because of the amount of code needed to achieve something so simple. I would probably end up creating a wrapper around each input element (painful to me).

Approach #2

Wrap the input in a component that uses shouldComponentUpdate to stop updates until onBlur. This again requires a wrapper around every input.

Approach #3

Make the input an uncontrolled component by taking out the value=. I haven't fully thought this through but I think I'll very quickly miss the features of the controlled component. If there was a way to switch between controlled and uncontrolled onFocus and onBlur maybe that's my answer.

Your Solution

There must be a simple way to solve this, or at least something in development within the react community, I just can't find anything. How have you solved this?

scms
  • 93
  • 1
  • 6
  • I think my first example was confusing people because it was a little oversimplified. I've swapped it out for a 2 layer example. The `App` here should only set state.number to a valid number (not a string). – scms Jun 18 '16 at 04:48
  • Any updates on this ? I have the same problem with an input that shouldn't have a value under a certain number. Validations in onChange obviously give weird behavior when you erase and the value goes lower than the minimum. – Geoffrey H Oct 17 '16 at 18:59
  • Ended up using Approach 1, quite painful for something so simple indeed. But I don't really see any other way at this point. – Geoffrey H Oct 17 '16 at 19:11
  • 1
    Yes @GeoffreyHug, I have as well resorted to approach 1 in multiple scenarios. That temporary state variable really bothers me though! I haven't yet attempted to create the wrapper I mentioned. Wouldn't you expect this to be a common issue within the react community? – scms Oct 18 '16 at 04:34

3 Answers3

0

To validate onBlur you can change your input from this:

<input type='text' value={this.state.number} onChange={this.handleNumberChange} />

to this:

<input type='text' onBlur={this.handleNumberChange} />

mariocatch
  • 8,305
  • 8
  • 50
  • 71
  • Does this answer your question? Couldn't really follow what you were asking. – mariocatch Jun 15 '16 at 18:32
  • This is my approach #3, make this uncontrolled by removing value=. I then need a way to update this as the data changes (the reason I want it controlled). – scms Jun 18 '16 at 04:40
0

You can use the following code instead of

var App = React.createClass({
    getInitialState: function() {
        return {number: 0}
    },    
    handleNumberChange: function(e) {
        var val = parseFloat(e.target.value);
        this.setState({number: val});
    },
    render: function() {
        return (
            <form className="reactForm" >
                <input type='text'
                       value={this.props.number}
                       onBlur={this.handleNumberChange} />
                <span>{this.state.number}</span>
            </form>
        );
    }
});

App.defaultProps = { number: 0 };

React.render(
    <App />,
    document.getElementById('app')
);

Note: To show value from some others then you have set it as like:

React.render( <App number="10"/>, document.getElementById('app') );

Any type of value you can send to the component as a props.

Bilas Sarker
  • 459
  • 3
  • 10
  • This is a approach you can use it. – Bilas Sarker Jun 15 '16 at 18:52
  • I realised I left out an approach. I've now added #0. That was the first thing I tried, which is essentially what you've done. I believe you meant to change `value=` to `defaultValue=` and make this an uncontrolled element which is my approach #3. – scms Jun 18 '16 at 04:39
  • Yes I just separated/set the default value as a props and onBlur handled and update the state value. – Bilas Sarker Jun 18 '16 at 05:05
  • I see that, and it works when the number is changed through the input, but it does not render a change in state.number from another source (like new data from the server). – scms Jun 18 '16 at 06:48
  • For server side data you can Inject e.g function mapStateToProps(state) { return { number: state.number } } export default connect(mapStateToProps)(App). This will convert state to props by redux.js. – Bilas Sarker Jun 18 '16 at 07:10
  • Adding redux like you say only ensures props.number is updated. But that props update will not update an uncontrolled (i.e. no `value` prop) . – scms Jun 18 '16 at 07:22
  • Also, if you say your suggestion uses a controlled (because you actually do have a `value` prop on your ), then please refer to my Approach #0. If you can solve that error then my problem is solved. – scms Jun 18 '16 at 07:24
0

Your initial approach is very close to working. Simply change value to defaultValue.

<input type='text' defaultValue={this.props.number} onChange={this.props.onNumberChange} />
johnpolacek
  • 2,599
  • 2
  • 23
  • 16
  • This will validate on every character change, not when the user leaves the control – Red Aug 27 '19 at 10:07