React form validation

This is a step-by-step tutorial that will show you how to do basic form validation in React.

You can see the full code on Github and see the app running on Heroku.

We’ll use create-react-app to get up and running quickly with a simple React app.

Install the package from npm and create a new app:

$ npm install -g create-react-app
$ create-react-app react-form-validation-demo
Now let’s run the app:

$ cd react-form-validation-demo/
$ npm start
That opens http://localhost:3000/ where our new app is running.

Next, let’s add bootstrap so that we can style our form easily:

$ npm install react-bootstrap — save
$ npm install [email protected] — save
Import Bootstrap CSS and optionally Bootstrap theme CSS in the beginning of the src/index.js file:

import ‘bootstrap/dist/css/bootstrap.css’;
import ‘bootstrap/dist/css/bootstrap-theme.css’;
Ok, now let’s build the core of our demo app. Let’s add a Form component.

In src/App.js, let’s replace the default intro text markup with a Form component that we’re going to build. We also need to import it:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Form from './Form.js';class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>React Form Validation Demo</h2>
        </div>
        <Form />
      </div>
    );
  }
}export default App;
Now, let’s create that Form component in src/Form.js.

Want to learn how to do form validation with React in a Rails app?

Learn now with hands-on videos in The Complete React on Rails course

React Form validation in Ruby on Rails
You will build a working calendar appointments app with validations for multiple form fields

We’ll make a simple sign up form with email and password input fields and a submit button.

import React, { Component } from ‘react’;
import ‘./Form.css’;class Form extends Component {
 render () {
   return (
     <form className=”demoForm”>
       <h2>Sign up</h2>
       <div className=”form-group”>
         <label htmlFor=”email”>Email address</label>
         <input type=”email” className=”form-control”
           name=”email” />
       </div>
       <div className=”form-group”>
         <label htmlFor=”password”>Password</label>
         <input type=”password” className=”form-control”
           name=”password” />
       </div>
       <button type=”submit” className=”btn btn-primary”>
          Sign up
       </button>
     </form>
   )
 }
}
export default Form;
I’ve copied an example form from the Bootstrap docs so that it looks nice out of the box. 

But notice I had to change some things to make it work with JSX.

class and for are reserved keywords in JavaScript, so we have to use className and htmlFor instead respectively. 

We also need to make sure we close all tags including the input tag.

Now, let’s initialise the state in the constructor:

constructor (props) {
  super(props);
  this.state = {
    email: '',
    password: ''
  }
}
We’re setting email and password to empty strings.

We’ll hook up the form input fields to these state values, for email:

value={this.state.email}
and for password:

value={this.state.password}
But we’re not updating the state on user input, so if we type in the form fields now, our text won’t appear.

We need an onChange handler for the input fields:

onChange={(event) => this.handleUserInput(event)}
which we’ll define as:

handleUserInput (e) {
  const name = e.target.name;
  const value = e.target.value;
  this.setState({[name]: value});
}
Ok, now let’s get to the validation.

We’ll save any validation errors and the validity status of the form in the state.

So let’s add couple of properties to our initial state:

constructor (props) {
  super(props);
  this.state = {
    email: '',
    password: '',
    formErrors: {email: '', password: ''},
    emailValid: false,
    passwordValid: false,
    formValid: false
  }
}
We’re adding a property called formErrors which will be an object with the input field names as keys and any validation errors as their values. The initial value for each key is an empty string.

We also have 3 boolean properties - emailValidpasswordValid and formValid, which we’ll use to enable or disable the form submit button, based on our validation results. We set their initial values to false.

Now let’s add a new component called FormErrors for displaying any errors from our validation above the form.

<div className=”panel panel-default”>
 <FormErrors formErrors={this.state.formErrors} />
</div>
We’ll save it in src/FormErrors.js:

import React from ‘react’;export const FormErrors = ({formErrors}) =>
  <div className='formErrors'>
    {Object.keys(formErrors).map((fieldName, i) => {
      if(formErrors[fieldName].length > 0){
        return (
          <p key={i}>{fieldName} {formErrors[fieldName]}</p>
        )        
      } else {
        return '';
      }
    })}
  </div>
It’s a stateless functional component (or presentational component) which simply iterates through all the form validation errors and displays them.

Now, we’ll call a validation after the user types in the field.

The setState method takes a callback function as a second argument, so let’s pass a validation function to it.

handleUserInput (e) {
  const name = e.target.name;
  const value = e.target.value;
  this.setState({[name]: value}, 
                () => { this.validateField(name, value) });
}
Let’s define that:

validateField(fieldName, value) {
  let fieldValidationErrors = this.state.formErrors;
  let emailValid = this.state.emailValid;
  let passwordValid = this.state.passwordValid;

  switch(fieldName) {
    case 'email':
      emailValid = value.match(/^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i);
      fieldValidationErrors.email = emailValid ? '' : ' is invalid';
      break;
    case 'password':
      passwordValid = value.length >= 6;
      fieldValidationErrors.password = passwordValid ? '': ' is too short';
      break;
    default:
      break;
  }
  this.setState({formErrors: fieldValidationErrors,
                  emailValid: emailValid,
                  passwordValid: passwordValid
                }, this.validateForm);
}

validateForm() {
  this.setState({formValid: this.state.emailValid && this.state.passwordValid});
}

We do two different checks for the input fields. For the email field, we check it against a regular expression to see if it’s an email. 

Note: I’m using an example regex from the Devise library. Checking for emails with a regex is very complex, but this simple regex will do for our toy app.

For the password field, we check if the length is a minimum of 6 characters or not.

When the field doesn’t pass the check, we set an error message for it and set its validity to false.

Then we call setState to update the formErrors and the field validity and we pass the validateForm callback to set the value of formValid.

Let's set the disabled attribute of the submit button based on the value of the formValid state property.

<button type="submit" className="btn btn-primary" 
  disabled={!this.state.formValid}>Sign up</button>

So with that, our basic form validation works.

We can add one little enhancement by highlighting the input fields when they have an error.

We’ll add the bootstrap has-error class to a field’s form-group based on its error value:

<div className={`form-group
                 ${this.errorClass(this.state.formErrors.email)}`}>
errorClass is a method we can define as:

errorClass(error) {
   return(error.length === 0 ? '' : 'has-error');
}
Now when a field has an error, it has a red border around it.

This is a very simple example. We have hard-coded field names in our validation code, which is not ideal or scalable. We can also only have one validation per field, which is also not practical.

If you want to learn how to generalise our validation to handle more than 2 fields and multiple errors, have a look at The Complete React on Rails Course.