React Form Validation - Disabling Submit Button

Let's take advantage of React controlled inputs and use it to disable our submit form before validation is passed


Now that we have established the difference between controlled and uncontrolled inputs, let's explore more about the use of controlled inputs in creating forms with React. This will be a short post focusing on disabling form submission until all input validations are passed.

Starting Code

We will use this simple registration form as our example. You can fork the Codepen into your account or copy it into your create-react-app application folder

Our goal is to disable the submit button until the following conditions are met:

  1. Email box is not empty and its value matches valid email format
  2. Username box is not empty
  3. Password box is not empty and its value is at least 5 characters long

Let's get started!

Using Controlled Inputs Real Time Data Change

As we have seen previously, React's controlled input use state for retrieving and updating its data. This ensures that our app will always have the most up to date values of the inputs. We can make use of React event handlers to setState AND validate certain conditions are met every time our state values are changed:

Controlled input validation
Working on input validation

From the diagram above, all we have to do is that we have to add a function named canSubmit that will evaluate our state. If all input value state passed the condition, we will need to enable submit button, if not then it's disabled. The canSubmit function will be called from our handleChange function.

  handleChange(event) {
    this.setState({
      // use dynamic name value to set our state object property
      [event.target.name]: event.target.value
    })
    this.canSubmit()
  }
  
  canSubmit() {
    const { email, username, password } = this.state
    if (email.length > 0 && username.length > 0 && password.length >= 5) {
      // validation passed! what to do?
      // ??????
    }
  }

Now that we have our canSubmit function. We can think of another logic we have to code here. What should the app do when the validation passed?

The obvious answer is that we have to enable submit button. But right now, our button IS enabled. It seems we have to disable submit button first, and then at the moment our canSubmit condition is passed, we then enable the button. Can you think of a way to implement this logic into our sample app?

Using state

You're right! We will create another state property canSubmit, which will act as the "security guard" over our submit button. Let's update our constructor method, since it's where state is initialized:

this.state = {
  email: '',
  username: '',
  password: '',
  isSubmitDisabled: true,
};

Now that our app has a new state to observe our validation test, let's update the canSubmit function.

  canSubmit() {
    const { email, username, password } = this.state
    if (email.length > 0 && username.length > 0 && password.length >= 5) {
      this.setState({
        isSubmitDisabled: false
      })
    }
    else {
      this.setState({
        isSubmitDisabled: true
      })
    }
  }

Also for our submit button:

<button className="btn btn-success btn-block" disabled={this.state.isSubmitDisabled}>Sign up</button>

With this update, the "security guard" is online and will guard our submit button until canSubmit function evaluates to true.

Fill your textboxes and you'll see the submit button turns enabled

Umm.. it seems something is off with our validation ??

Oh you're right.. It seems our canSubmit function is called before our state is updated. Why is that? A quick look into React setState documentation will tell you:

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied.

setState is asynchronous, and since we did call on this.state immediately in our canSubmit function, we end up using our state values before it's updated. As the documentation says, let's use a setState callback in our handleChange function.

We cannot use componentDidUpdate lifecycle method because it is called each time we call setState, and we do call setState inside canSubmit, so using it will cause an infinite loop and break our app 👻

  handleChange(event) {
    this.setState({
      // use dynamic name value to set our state object property
      [event.target.name]: event.target.value
    }, function(){ this.canSubmit()})
    
  }

Now it works perfectly.

A note on onSubmit event handler

Some of you might notice that onSubmit function is not bound on construct like onChange function. This is intentional to show a different approach in using function in an ES6 class. Arrow function can work without bind but not regular function, since a regular function will lose the context of this.

This topic is something more to do with ES6 rather than React, the takeaway for us is that when creating a function in a React component class, we have to bind regular function. Which one to use is up to you and the circumstance surrounding your class logic.

Another thing to note, once you submitted the form, there is a minor blip in the UI caused by the re-render of our app. This is fine for our exercise, since all we do is creating an alert box. But for real application where we want the app to do specific process, this is absolutely not OK. That's why in many React sample code you'll find e.preventDefault() or event.preventDefault() written inside a submit function.

  handleSubmit = (event) => {
    event.preventDefault()
    const { email, username, password } = this.state
    alert(`Your registration detail: 
           Email: ${email} 
           Username: ${username} 
           Password: ${password}`)
  }

This will prevent JavaScript event handler method from our browser to run.

Summary

Let's recap what we have learned in this post:

  1. Adding a validation function to our event handler function
  2. Using setState callback to validate our updated state
  3. Using arrow function and bind method for our event handler

Notice that we haven't done the valid email format test. I will include that in the next post, in which we will improve our form's interactivity by displaying validation errors and highlighting the input element that isn't passing its validation test. You can subscribe to my newsletter and be notified when the new post is available. 🔔

For now though, can you think of a good way to include valid email format test in our app? Fork the finished codepen below, try this exercise and then share your pen in the comments.

Happy coding! 🤓


Get my weekly newsletter ✉️
Let's explore the exciting world of React together.
No Spam. Unsubscribe anytime