React js input invalid while user types (I want to validate onBlur instead of onChange)

4.7k Views Asked by At

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?

3

There are 3 best solutions below

2
mariocatch On

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} />

7
Bilas Sarker On

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.

1
johnpolacek On

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

<input type='text' defaultValue={this.props.number} onChange={this.props.onNumberChange} />