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:

how are we going to check if the data is valid?

how are we going to display errors, if any?

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:

display a ❌ or mark red the inputs that contain bad data

display a list of errors at the top of the form

display errors right next to the relevant inputs

something else

any combination of the above

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 > ); }

(The error wording is important. Read this amazing piece on what makes a good error message.)

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:

the name is not empty

the email address is at least 5 characters long, contains only one @ , and at least one dot

, and at least one dot the password is at least 6 characters long

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:

assign ref s to the inputs;

s to the inputs; grab the value of each input inside handleSubmit by its ref.

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 > ); } }

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 );

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?