The Complete Guide to Forms in React
A letter about React forms to me in the future
If you prefer a more elaborate video tutorial on handling forms in ReactJs(includes using controlled, uncontrolled, class component, functional component also how to use form library called REACT HOOK FORM), you can check out my youtube channel, everything in this post is covered there and I published the complete course on udemy.
Forms are very useful in any web application. Unlike with Angular and AngularJS, which give you form validation out of the box, you have to handle forms yourself in React. This brings about many complications, like how to get form values, how to manage form state, and how to validate a form on the fly and show validation messages.
There are different methods and libraries out there to help with this, but if you’re like me and hate to be dependent on too many libraries, welcome on board. We’re going to bootstrap our own form from the ground up.
There are two types of form input in React. We have the uncontrolled input and the controlled input. The uncontrolled input are like traditional HTML form inputs, in that they remember what you typed. We will use ref to get the form values.
We added ref="name" to the input tag so that we can access the value with this.refs.name.value when the form is submitted. The downside to this is that you have to pull the value from the field when you need it, and this can happen when the form is submitted.
The controlled input is when the React component that renders a form also controls what happens in that form on subsequent user input. This means that, as form value changes, the component that renders the form saves the value in its state.
Of course, another component can handle the form state. The goal is that each time the input changes, the method changeHandler is called and will store the input state. Hence the component always has the current value of the input without needing to ask for it. This means that the form component can respond to input changes immediately; for example:
- in-place feedback, like validation
- disabling the button unless all fields have valid data
- enforcing a specific input format
Handling Multiple Form Inputs
In most situations, we are going to have more than one form input. We need a way to capture the input with a method instead of declaring multiple methods to do this. Hence we are going to modify the changeHandler as shown below:
Because of the way the changeHandler has been modified above, our form input can reference it to update it states dynamically.
The value of the name attribute on each input must be the same, with the state name declared in the formControls in the constructor.
Create a TextInput Component
There are different input elements, e.g., text, email, password, select option, checkbox, date, and radio button. I love to create separate custom components for the input elements. Let’s start with the text input type.
Notice the {…props}. We use this to distribute the props to the input element. We can use the custom text input element like below:
Validating Our Custom TextInput
Since we are using controlled input, we can add more keys to our formControls state to help validate the input. We need the valid property to denote if the input is valid or not. validationRules contains the list of the rules to be checked before the input is valid.
Our aim is that each time the input changes, we make sure the validationRules for that input is checked for true or false, and then update the valid key with the result of the check. We also added the touched property to denote that the user has touched the form input. This will help with displaying validation feedback when the input has been touched. The check will be done in the changeHandler method like below:
valid is equated to the method validate(value, prevState.formControls[name]).validationRules), which we will use to check whether the valid status for a particular control is true or false.
I move the validate method to a separate class and then import it. The validate method accepts two parameters, the value and the rules. We loop through the rules and check whether each rule is valid, returning true when valid and false when invalid.
Let’s assume we want to add another validation on name, e.g., we wantname to be required. All we need do is update the formControl for name validationRules, and write the logic for it in the validator class like below:
Then we need to update the validator class to accommodate the required validator.
We created a custom TextInput, and we created a formControl that has a property named name with a validation rule of isRequired and minLength of 3. Below is the component that handles this:
If we click the Submit button after filling the TextInput, the formSubmitHandler will console the formControls value like below:
The good thing is that we do not have to wait until the user clicks Submit before we know whether the form input is valid or not. Since this is actually stored in the component state, we can use it to display an error message or feedback when the user is typing. We can even disable the Submit button until the validation passes.
Displaying Error Feedback
To display error feedback on the input, we need to pass the touched and valid properties for that particular input as a prop to its component. We will add the error style based on the valid status. We want to do this only when the input has been touched.
We also need to modify our TextInput component to display the style based on the value of props.valid and props.touched.
Please note that you should have added the form-control and control-error style into the App.css.
You should see a screenshot like below if your TextInput is invalid and has been touched.
Disable Submit Button if the Form Is Invalid
HTML 5 has a disabled property on button input. We can equate our formControls property valid status to the disabled property as long as the formControls is not valid.
The disabled={!this.state.formControls.name.valid} will work fine if we have only one form control, but if we need to handle more than one form control, we can set a new property to the state that will keep track of the validity status of the whole formControl object. We need to update our state to accommodate this:
We need to update the changeHandler method so that we can loop through the form controls status and, when valid, update the formIsValid status to true.
With this setup, it will be easier for us to set the disabled property to formIsValid status, and this will handle one or more form objects.
Considering Other Form Input Types
Text Area: The text area, email, and password will work similarly to text input. We can create a TextArea component.
Email: We can also create an Email component, just like TextInput.
Password: We can also create a Password component, just like TextInput.
The email, text area, and password form control will look similar to the text input form input:
Select Option: The select option form control is slightly different from the other form controls because we have to accommodate the selection options. It will look like below:
Then the select option component will look like below:
Radio: The radio input is similar to the select option since it’s only one option that can be selected out of the available options. The form control will be similar to the select option form control. Below is how the radio button component looks:
Putting it all together, let’s assume we want to have an email input, name (TextInput), gender (select option), and radio input all in form control. Below is an example of what your component will look like:
Thanks for reading.

