Validating a React form upon submit

05 Sep 2017

Validating a React form upon submit

You now know about the differences between the controlled vs. uncontrolled form inputs.

You have just made a couple of forms for your app when something stroke you:

Users are going to enter bad data into the inputs… How do I handle that?

Because, let’s face it, we don’t live in a perfect world, where everyone goes by your rules, or even knows them. And would there even be forms in the perfect world if things were so perfect the website knew everything about you, already?

So yeah, your users are going to skip some fields; enter emails without a @; use weak passwords; and not agree with the Terms of Service… But these are all crucial requirements. We need to show the users what went wrong when they enter invalid data.

In this post, we are going to look at the most basic way to do that. It’s been around for decades, you surely have seen it:

You fill in the form, click submit, and then you either see everything went well, or see the form again, with error messages.

For now, the errors won’t be coming from the server, but instead, will be generated by our component.

So you have a form

It doesn’t matter whether it’s made of controlled or uncontrolled inputs. Either is fine for this technique. And the article will include examples of both.

Ok, so the form is there. However, there still are two pieces of the puzzle missing:

And answers to both are going to greatly depend on your specific requirements.

But there are pretty generic concepts and techniques for both, with I’m about to show.

Showing errors

Wait, why do we start at the end? Because it’s important to visualize the result you’re going for. So let’s start there, and then see how can we make this happen.

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 this post, it doesn’t matter as much!

The way you want to display the errors will, in a way, influence how you represent them.

To mark red the bad inputs, this will suffice:

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

Where false means there are no errors.

To display a list of errors at the top, we are going to need something a bit different:

errors: [
  "email should contain a @",
]

And to display errors inline, we would want something like this:

errors: {
  name: [],
  email: ["should contain a @"],
}

Then, in the form component, we will reference this errors object and display the errors as we see fit:

render() {
  const errors = (somehow get them);

  return (
    <form ...>
      (display inputs and errors)
    </form>
  );
}

Validation in a nutshell

Validation can be thought of as a box, or a function, that takes in user inputs, and says if the data is valid.

validate(inputs) = ok | notOk

What it does exactly with the inputs — that is up to you. What it returns exactly — that’s up to you too.

Now, how exactly that function checks the inputs actually depends on what you want to achieve. How exactly it represents errors? that depends on you, mostly.

It sounds pretty generic… because there is no one True Way™ for anything.

What we do, then, is run this function when the form submitted, and if there are errors, we somehow reflect that in the UI.

class SomeForm extends Component {
  // ...
  handleSubmit = (e) => {
    const value1 = ...;
    const value2 = ...;

    const errors = validate(value1, value2, ...);
    const hasErrors = ...;
    if (hasErrors) {
      // do something with errors
      return;
    }

    // send the form...
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        ...
      </form>
    );
  }
  // ...
}

Could just show an error icon. Could highlight the fields with errors. Could place an error message next to the input with erroneous data… Anything!

A sign up form case

The theory is good and everything, but there’s no substitute for specific code.

So imagine we have a sign up form, with three fields: name, email, and password.

When that form is submitted, we want to make sure that:

What could that validation function look like?

function validate(name, email, password) {
  // we are going to store errors for all fields
  // in a signle array
  const errors = [];

  if (name.length === 0) {
    errors.push("Name can't be empty");
  }

  if (email.length < 5) {
    errors.push("Email should be at least 5 charcters long");
  }
  if (email.split('').filter(x => x === '@').length !== 1) {
    errors.push("Email should contain a @");
  }
  if (email.indexOf('.') === -1) {
    errors.push("Email should contain at least one dot");
  }

  if (password.length < 6) {
    errors.push("Password should be at least 6 characters long");
  }

  return errors;
}

Nice! With that, let’s see how a form component could use this function.

As you may remember, both controlled and uncontrolled form inputs allow us to validate inputs on submit. Let’s see how both could look like.

Uncontrolled

For the uncontrolled approach to work, we will simply:

class SignUpForm extends Component {
  constructor() {
    super();
    this.state = {
      errors: [],
    };
  }

  handleSubmit = (e) => {
    e.preventDefault();

    const name = ReactDOM.findDOMNode(this._nameInput).value;
    const email = ReactDOM.findDOMNode(this._emailInput).value;
    const password = ReactDOM.findDOMNode(this._passwordInput).value;

    const errors = validate(name, email, password);
    if (errors.length > 0) {
      this.setState({ errors });
      return;
    }

    // submit the data...
  };

  render() {
    const { errors } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        {errors.map(error => (
        <p key={error}>Error: {error}</p>
        ))}
        <input
          ref={nameInput => this._nameInput}
          type="text"
          placeholder="Name"
        />
        <input
          ref={emailInput => this._emailInput}
          type="text"
          placeholder="Email"
        />
        <input
          ref={passwordInput => this._passwordInput}
          type="password"
          placeholder="Password"
        />

        <button type="submit">Submit</button>
      </form>
    );
  }
}

JS Bin on jsbin.com

Controlled

The controlled form is going to look almost the same, except for the fact that, instead of finding DOM nodes, we will be using the inputs values directly from this.state.

We are also going to pass value=... to each input, as well as an onChange handler to update the state whenever the input changes.

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

      errors: [],
    };
  }

  handleSubmit = (e) => {
    e.preventDefault();

    const { name, email, password } = this.state;

    const errors = validate(name, email, password);
    if (errors.length > 0) {
      this.setState({ errors });
      return;
    }

    // submit the data...
  }

  render() {
    const { errors } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        {errors.map(error => (
          <p key={error}>Error: {error}</p>
        ))}
        <input
          value={this.state.name}
          onChange={evt => this.setState({ name: evt.target.value })}
          type="text"
          placeholder="Name"
        />
        <input
          value={this.state.email}
          onChange={evt => this.setState({ email: evt.target.value })}
          type="text"
          placeholder="Email"
        />
        <input
          value={this.state.password}
          onChange={evt => this.setState({ password: evt.target.value })}
          type="password"
          placeholder="Password"
        />

        <button type="submit">Submit</button>
      </form>
    );
  }
}

ReactDOM.render(<SignUpForm />, document.body);

JS Bin on jsbin.com

Nice!

Exercise: Improved validation function

Wouldn’t it be nice if we knew which error related to which input? Perhaps so that we could display it right next to the input?

Can you change the validate function and the component in such a way that we would know which error is about which field?

Think your friends would dig this article, too?

Google+
Tumblr
Preview

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 React and see how to implement common form patterns.

The excerpt contains a table of contents and two chapters: on building an intuition for forms and handling various form controls.

No spam. I will occasionally send you my posts on JavaScript and React.
Preview

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 React and see how to implement common form patterns.

The excerpt contains a table of contents and two chapters: on building an intuition for forms and handling various form controls.