Instant form field validation with React's controlled inputs

03 Jan 2017

If you’ve followed along, you know about controlled inputs and simple things they enable, like disabling the Submit button when some fields are missing or invalid.

We’re not stopping there, of course!

While a disabled button is nice, the reason for that is not immediately apparent to the users. They have no idea what’s their mistake here. They don’t even know which field is causing that…

And that ain’t pretty. We absolutely have to fix that!

As a little refresher from before:

Using controlled inputs implies we are storing all the input values in our state. We can then evaluate a particular condition on every value change, and do something based on it. Previously all we did was disable the button.

We used a simple expression to compute whether the button should be disabled (aka when either of email or password was empty):

const { email, password } = this.state;
const isEnabled =
  email.length > 0 &&
  password.length > 0;

<button disabled={!isEnabled}>Sign up</button>

It got the job done. Now, to mark the bad inputs, we need to ask ourselves a couple of questions.

How are the errors going to be shown?

This is an important question to ask yourself, as different requirements might warrant different error representations.

There are many ways to show input errors. For example, you could:

Which one should you use? Well, it’s all about the experience you want to provide. Pick what you want.

For the purpose of this post, I’m going to do with the simplest one — marking red the bad inputs, without anything else.

How to represent errors?

The way you want to display errors influences how you might represent them.

To indicate whether a particular input is valid, without any additional information as to why it is invalid, something like this will suffice:

errors: {
  name: false,
  email: true,
}

false means no errors aka entirely valid; true means a field is invalid.

In future, if we decide we need to store the reason something was invalid, we can replace true/false here with a string containing an error message.

But how is this error object created?

Now that we know how we want to display the errors AND know how to represent them, there’s something crucial missing.

How to actually get errors?

Or, another way to put it is, how to take existing inputs, validate then and get the error object we need?

We are going to need a validation function for that. It will accept the current values of the fields and returns us the errors object.

Continuing the sign up form example, recall we had this:

const { email, password } = this.state;
const isEnabled =
  email.length > 0 &&
  password.length > 0;

We can, in fact, turn that piece of logic into a valudation function that will:

function validate(email, password) {
  // true means invalid, so our conditions got reversed
  return {
    email: email.length === 0,
    password: password.length === 0,
  };
}

The remaining piece

There’s one piece of the puzzle remaining.

We have a validation function and know how we want to show errors. We also have a form.

It’s time to connect the dots now.

JS Bin on jsbin.com

One more thing

If you look at the JS Bin above, you may notice something odd. The fields are marked red by default, because empty fields are invalid but…

We never even gave a user a chance to type first! Also, the fields are still red while focused for the first time.

This is bad-ish for UX.

We are going to do that by adding the error class if the field was in focus at least once but has since been blurred.

This ensures that the first time a user focuses the field, the error won’t appear right away, but instead, only when the field is blurred. On subsequent focuses, though, the error would be shown.

This is easily achievable by using the onBlur event, and state to keep track of what was blurred.

class SignUpForm extends React.Component {
  constructor() {
    super();
    this.state = {
      email: '',
      password: '',
      touched: {
        email: false,
        password: false,
      },
    };
  }

  // ...

  handleBlur = (field) => (evt) => {
    this.setState({
      touched: { ...this.state.touched, [field]: true },
    });
  }

  render()
    const shouldMarkError = (field) => {
      const hasError = errors[field];
      const shouldShow = this.state.touched[field];

      return hasError ? shouldShow : false;
    };

    // ...

    <input
      className={shouldMarkError('email') ? "error" : ""}
      onBlur={this.handleBlur('email')}

      type="text"
      placeholder="Enter email"
      value={this.state.email}
      onChange={this.handleEmailChange}
    />
  }
}

JS Bin on jsbin.com

Not so hard, right?

Final touches

Note that shouldMarkError only affects field presentation. The status of the submit button still depends only on validation errors.

A nice final touch might be to force display of errors on all fields, regardless of whether they have been in focus, when the user hovers or clicks a disabled submit button.

Implementing this is left as an exercise for you.


If you are digging this series on handling forms with React, subscribe below to get new posts straight in your inbox.

Get a free sample of The Missing Forms Handbook of React

You know how you tear your hair out when your designer asks to make a nice form?

But what if you could implement the form experience your users deserve?

The Missing Forms Handbook of React can help you breeze through your React forms. You will learn how forms fit into the React model. And, you are going to see how to implement common form patterns and structures.

No spam, promise. I hate it as much as you do!

, enjoying the article? Now think of 3 friends who are interested in React, Forms and would be into it, and share the link with them! 👇

https://goshakkk.name/instant-form-fields-validation-react/