How to properly validate input values with React.JS?

First, here is an example of what I'll mention below: http://jsbin.com/rixido/2/edit

How to properly validate input values with React.JS?

However you want. React is for rendering a data model. The data model should know what is valid or not. You can use Backbone models, JSON data, or anything you want to represent the data and it's error state.

More specifically:

React is generally agnostic towards your data. It's for rendering and dealing with events.

The rules to follow are:

  1. elements can change their state.
  2. they cannot change props.
  3. they can invoke a callback that will change top level props.

How to decide if something should be a prop or a state? Consider this: would ANY part of your app other than the text field want to know that the value entered is bad? If no, make it a state. If yes, it should be a prop.

For example, if you wanted a separate view to render "You have 2 errors on this page." then your error would have to be known to a toplevel data model.

Where should that error live?
If your app was rendering Backbone models (for example), the model itself would have a validate() method and validateError property you could use. You could render other smart objects that could do the same. React also says try to keep props to a minimum and generate the rest of the data. so if you had a validator (e.g. https://github.com/flatiron/revalidator) then your validations could trickle down and any component could check props with it's matching validation to see if it's valid.

It's largely up to you.

(I am personally using Backbone models and rendering them in React. I have a toplevel error alert that I show if there is an error anywhere, describing the error.)


You can use npm install --save redux-form

Im writing a simple email and submit button form, which validates email and submits form. with redux-form, form by default runs event.preventDefault() on html onSubmit action.

import React, {Component} from 'react';
import {reduxForm} from 'redux-form';

class LoginForm extends Component {
  onSubmit(props) {
    //do your submit stuff
  }


  render() {
    const {fields: {email}, handleSubmit} = this.props;

    return (
      <form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
        <input type="text" placeholder="Email"
               className={`form-control ${email.touched && email.invalid ? 'has-error' : '' }`}
          {...email}
        />
          <span className="text-help">
            {email.touched ? email.error : ''}
          </span>
        <input type="submit"/>
      </form>
    );
  }
}

function validation(values) {
  const errors = {};
  const emailPattern = /(.+)@(.+){2,}\.(.+){2,}/;
  if (!emailPattern.test(values.email)) {
    errors.email = 'Enter a valid email';
  }

  return errors;
}

LoginForm = reduxForm({
  form: 'LoginForm',
  fields: ['email'],
  validate: validation
}, null, null)(LoginForm);

export default LoginForm;

Your jsfiddle does not work anymore. I've fixed it: http://jsfiddle.net/tkrotoff/bgC6E/40/ using React 16 and ES6 classes.

class Adaptive_Input extends React.Component {
  handle_change(e) {
    var new_text = e.currentTarget.value;
    this.props.on_Input_Change(new_text);
  }

  render() {
    return (
      <div className="adaptive_placeholder_input_container">
        <input
          className="adaptive_input"
          type="text"
          required="required"
          onChange={this.handle_change.bind(this)} />
        <label
          className="adaptive_placeholder"
          alt={this.props.initial}
          placeholder={this.props.focused} />
      </div>
    );
  }
}

class Form extends React.Component {
  render() {
    return (
      <form>
        <Adaptive_Input
          initial={'Name Input'}
          focused={'Name Input'}
          on_Input_Change={this.props.handle_text_input} />

        <Adaptive_Input
          initial={'Value 1'}
          focused={'Value 1'}
          on_Input_Change={this.props.handle_value_1_input} />

        <Adaptive_Input
          initial={'Value 2'}
          focused={'Value 2'}
          on_Input_Change={this.props.handle_value_2_input} />
      </form>
    );
  }
}

class Page extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      Name: 'No Name',
      Value_1: '0',
      Value_2: '0',
      Display_Value: '0'
    };
  }

  handle_text_input(new_text) {
    this.setState({
      Name: new_text
    });
  }

  handle_value_1_input(new_value) {
    new_value = parseInt(new_value);
    var updated_display = new_value + parseInt(this.state.Value_2);
    updated_display = updated_display.toString();
    this.setState({
      Value_1: new_value,
      Display_Value: updated_display
    });
  }

  handle_value_2_input(new_value) {
    new_value = parseInt(new_value);
    var updated_display = parseInt(this.state.Value_1) + new_value;
    updated_display = updated_display.toString();
    this.setState({
      Value_2: new_value,
      Display_Value: updated_display
    });
  }

  render() {
    return(
      <div>
        <h2>{this.state.Name}</h2>
        <h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
        <Form
          handle_text_input={this.handle_text_input.bind(this)}
          handle_value_1_input={this.handle_value_1_input.bind(this)}
          handle_value_2_input={this.handle_value_2_input.bind(this)}
        />
      </div>
    );
  }
}

ReactDOM.render(<Page />, document.getElementById('app'));

And now the same code hacked with form validation thanks to this library: https://github.com/tkrotoff/react-form-with-constraints => http://jsfiddle.net/tkrotoff/k4qa4heg/

http://jsfiddle.net/tkrotoff/k4qa4heg/

const { FormWithConstraints, FieldFeedbacks, FieldFeedback } = ReactFormWithConstraints;

class Adaptive_Input extends React.Component {
  static contextTypes = {
    form: PropTypes.object.isRequired
  };

  constructor(props) {
    super(props);

    this.state = {
      field: undefined
    };

    this.fieldWillValidate = this.fieldWillValidate.bind(this);
    this.fieldDidValidate = this.fieldDidValidate.bind(this);
  }

  componentWillMount() {
    this.context.form.addFieldWillValidateEventListener(this.fieldWillValidate);
    this.context.form.addFieldDidValidateEventListener(this.fieldDidValidate);
  }

  componentWillUnmount() {
    this.context.form.removeFieldWillValidateEventListener(this.fieldWillValidate);
    this.context.form.removeFieldDidValidateEventListener(this.fieldDidValidate);
  }

  fieldWillValidate(fieldName) {
    if (fieldName === this.props.name) this.setState({field: undefined});
  }

  fieldDidValidate(field) {
    if (field.name === this.props.name) this.setState({field});
  }

  handle_change(e) {
    var new_text = e.currentTarget.value;
    this.props.on_Input_Change(e, new_text);
  }

  render() {
    const { field } = this.state;
    let className = 'adaptive_placeholder_input_container';
    if (field !== undefined) {
      if (field.hasErrors()) className += ' error';
      if (field.hasWarnings()) className += ' warning';
    }

    return (
      <div className={className}>
        <input
          type={this.props.type}
          name={this.props.name}
          className="adaptive_input"
          required
          onChange={this.handle_change.bind(this)} />
        <label
          className="adaptive_placeholder"
          alt={this.props.initial}
          placeholder={this.props.focused} />
      </div>
    );
  }
}

class Form extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      Name: 'No Name',
      Value_1: '0',
      Value_2: '0',
      Display_Value: '0'
    };
  }

  handle_text_input(e, new_text) {
    this.form.validateFields(e.currentTarget);

    this.setState({
      Name: new_text
    });
  }

  handle_value_1_input(e, new_value) {
    this.form.validateFields(e.currentTarget);

    if (this.form.isValid()) {
      new_value = parseInt(new_value);
      var updated_display = new_value + parseInt(this.state.Value_2);
      updated_display = updated_display.toString();
      this.setState({
        Value_1: new_value,
        Display_Value: updated_display
      });
    }
    else {
      this.setState({
        Display_Value: 'Error'
      });
    }
  }

  handle_value_2_input(e, new_value) {
    this.form.validateFields(e.currentTarget);

    if (this.form.isValid()) {
      new_value = parseInt(new_value);
      var updated_display = parseInt(this.state.Value_1) + new_value;
      updated_display = updated_display.toString();
      this.setState({
        Value_2: new_value,
        Display_Value: updated_display
      });
    }
    else {
      this.setState({
        Display_Value: 'Error'
      });
    }
  }

  render() {
    return(
      <div>
        <h2>Name: {this.state.Name}</h2>
        <h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>

        <FormWithConstraints ref={form => this.form = form} noValidate>
          <Adaptive_Input
            type="text"
            name="name_input"
            initial={'Name Input'}
            focused={'Name Input'}
            on_Input_Change={this.handle_text_input.bind(this)} />
          <FieldFeedbacks for="name_input">
            <FieldFeedback when="*" error />
            <FieldFeedback when={value => !/^\w+$/.test(value)} warning>Should only contain alphanumeric characters</FieldFeedback>
          </FieldFeedbacks>

          <Adaptive_Input
            type="number"
            name="value_1_input"
            initial={'Value 1'}
            focused={'Value 1'}
            on_Input_Change={this.handle_value_1_input.bind(this)} />
          <FieldFeedbacks for="value_1_input">
            <FieldFeedback when="*" />
          </FieldFeedbacks>

          <Adaptive_Input
            type="number"
            name="value_2_input"
            initial={'Value 2'}
            focused={'Value 2'}
            on_Input_Change={this.handle_value_2_input.bind(this)} />
          <FieldFeedbacks for="value_2_input">
            <FieldFeedback when="*" />
          </FieldFeedbacks>
        </FormWithConstraints>
      </div>
    );
  }
}

ReactDOM.render(<Form />, document.getElementById('app'));

The proposed solution here is hackish as I've tried to keep it close to the original jsfiddle. For proper form validation with react-form-with-constraints, check https://github.com/tkrotoff/react-form-with-constraints#examples