Client-side form validation in Rails with React

Hrishi Mittal
In this lesson, we’ll see how to do form validations in the browser using React.

It’s important to do validations on the server before saving user-submitted data but there are some things we can do on the client to stop the user from submitting invalid data.

This saves the user time and makes for a better user experience.

In the previous lesson, we added a constraint on the event title - it needs to be at least 3 characters long. But we only check if the value entered by the user passes this validation once they submit the form. So it takes a round trip to the server to tell the user that there’s a problem with the data.

We can easily check for this in the client with React and give the user real-time feedback about validation.

We can disable the form submit button unless the form field values are valid.

Let’s do that with the title field first.

In our EventForm component, let’s set the disabled attribute of the form submit button to a prop that we’ll pass to it.

<input type='submit' value='Make Event' disabled={!props.formValid} />
So we’ll pass this prop called formValid which is a boolean indicating whether the form is valid or not.

If it’s not valid we’ll set disabled to true, thus preventing the user from submitting the form.

We’re using this component in its parent Eventlite component. So that’s where we need to pass the formValid prop.

Let’s add it inside the EventForm tag in the render function of Eventlite:

  <EventForm handleSubmit = {this.handleSubmit}
    handleInput = {this.handleInput}
    formValid={this.state.formValid}
    title = {this.state.title}
    start_datetime = {this.state.start_datetime}
    location = {this.state.location} />
We’ll save formValid in the state and set its initial value to false in the constructor:

  constructor (props) {
    super(props)
    this.state = {
      events: this.props.events,
      title: '',
      start_datetime: '',
      location: '',
      formErrors: {},
      formValid: false
    }
  }

Now when the page first loads, the submit button is disabled by default.

Now, we need to watch for the user input in the form fields and update the state value formValid based on the validity of the input.

We already have the handleInput function which updates the state with the value of the input for each form field.

  handleInput = e => {
    e.preventDefault()
    const name = e.target.name
    const newState = {}
    newState[name] = e.target.value
    this.setState(newState)
  }

So let’s modify that to also trigger a validation.

The setState function accepts a second optional argument which is a callback function that’s executed once setState is completed and the component is re-rendered.

So we can pass a validation function as the second optional argument to setState. Let’s call it validateForm.

  handleInput = e => {
    e.preventDefault()
    const name = e.target.name
    const newState = {}
    newState[name] = e.target.value
    this.setState(newState, this.validateForm)
  }
And let’s define that function as follows:

validateForm() {
  this.setState({formValid: this.state.location.length > 0 && 
                            this.state.title.length > 2 && 
                            Date.parse(this.state.start_datetime) > Date.now()})
}
We check for three conditions - one for each for the fields. For title, we check if the length is greater than two. For location, we check if the length is greater than zero. And for start_datetime, we check if it’s in the future.

Now let’s refresh the page and test this in the browser.

The submit button is initially disabled. When we enter a valid title, start datetime and location, the button becomes enabled and we can successfully submit the form.


We’ve got validations for all our form fields working on the client side (instead of just on the server), but one thing we’ve lost in the process is the ability to display error messages when validations fail. So let’s add that back now.

We’ll modify the validateForm function to not only check if the inputs are valid, but also update the state with error messages corresponding to each failed validation.

We’ll use two variables formErrors and formValid to store the validation errors and the validity of the form inputs respectively.

Then, instead of the current simple conditional check on all the fields, we’ll check each field input one by one and update formErrors and formValid accordingly.

validateForm() {
  let formErrors = {}
  let formValid = true
  if(this.state.title.length <= 2) {
    formErrors.title = ["is too short (minimum is 3 characters)"]
    formValid = false
  }
  if(this.state.location.length === 0) {
    formErrors.location = ["can't be blank"]
    formValid = false
  }
  if(this.state.start_datetime.length === 0) {
    formErrors.start_datetime = ["can't be blank"]
    formValid = false
  } else if(Date.parse(this.state.start_datetime) <= Date.now()) {
    formErrors.start_datetime = ["can't be in the past"]
    formValid = false
  }
  this.setState({formValid: formValid, formErrors: formErrors})
}
Finally, we call setState to update the app state with the final values of formValid and formErrors.

Now, on initial load the form submit button is disabled and no error messages are displayed. But as we start typing in the input fields, the form validation kicks in and any failed validation error messages are displayed above the form.

So that’s some simple text field client side validation in React. Next, we’ll look at how to generalise these validations to make them more flexible.