How to Create a React Form: Controlled vs. Uncontrolled Components

In this article, we'll cover two ways to create React forms: the HTML way, with uncontrolled components, and the best practice, with controlled components.We'll discuss concepts such as: what are controlled and uncontrolled components, main tags used in a React form, and form validation and submission.

In HTML forms, data is handled by the DOM. In React, on the other hand, the best practice is to handle the data inside the components. This involves storing the data in the component’s state and controlling the changes with event handlers.

This may be hard to grasp in the beginning, as it requires you to think about form handling and validations a bit differently. However, it’s also more powerful and gives you more flexibility and control when working with more complex forms.

To help you understand the differences between the two methods, we’ll discuss both ways of handling forms in React: the HTML way, which uses “uncontrolled components”, and the best practice, with “controlled components”. 

To make things easier to visualize, we’ll build the same landing page for the two forms. 

WYWL: What You Will Learn#

By the end of this tutorial, you’ll be able to build a simple landing page with a functioning React form. 


You can find the repository for this tutorial here.

Prerequisites#

Although this tutorial is for beginners and explains the main concepts from scratch, in order to follow through it’s best if you have some knowledge of: 

If you're not familiar with these yet, please take a few minutes to go through the pages above before starting this tutorial.

Controlled vs. uncontrolled components in React#


In the browser, forms maintain their own internal state. The form data is handled by the DOM, so when you type something in an input field, for example, the data is "remembered" by the DOM.

To retrieve it, you need to "pull" the value of the field from the DOM, and this is usually done only once when the user hits the submit button. This type of form is called "uncontrolled" because it relies on the DOM to manage the input data.

In React, we use a virtual DOM which no longer observes all the changes that happen in the DOM. The virtual DOM only observes what we tell it to, so if we want our form data to be “seen”, we need to tell React about the form changes. 

Uncontrolled components in React are similar to HTML inputs, as they "remember" what you type in. Here's an example of a simple uncontrolled component.

If you want to access the data from uncontrolled components, you need to use a ref. Refs allow you to reference a DOM element or class component from within a parent component.

Refs allow you to “pull” the value from a field when you need it, so you can do it when the form is submitted, for example. This means less complex code for you, but it also means that you’re not able to handle validations in real-time. 

Also, if you opt for uncontrolled components, you can’t disable the submit button for example and you can’t enforce a specific format for the text input as the user types something in. 

All your validations will happen on submit, which can be quite annoying for the user, especially if you’re building longer forms with a lot of input fields. 

However, uncontrolled components are the easiest way to create forms, and if you don’t really need to do much validation, you can safely use them. 

With controlled components, you store the state in the component that renders the input, so in this case in the Form component. 

Each input field accepts its current value as a prop and has a callback function which is called when the state of the input changes.

Here's an example of a controlled class component.

The callback function is controlled by the parent component - so the Form in our case. The new values of the state are passed to the input fields as props. 

Since the value of the input field changes whenever you type a new character, so the state changes continuously. 

This may sound bad but it’s actually useful, as the Form component is always “aware” of the state and the value of the input. Thus, if you want to validate something, you don’t need to “pull” the state value on submit and then display error messages. 

For example, if you don’t want the e-mail address to include “@gmail”, you can display a validation message in real-time, and this contributes to a better user experience. 

Step-by-Step Tutorial: React Form With Controlled Components#


Let’s start by initiating a new React app, using Create React App. I’ll use VSCode for this tutorial. First type this command in your terminal: 

npx create-react-app form-demo

Then, move into the form-demo folder, and in the terminal, type npm start to run the app in your browser. 


Step 1: Create the App.js page layout#

To speed things up, I’ll use react-bootstrap for the layout of the landing page, and an icon from bigheads.io to make the page a bit nicer. You can grab the image here.

To install react-bootstrap, type npm install react-bootstrap in the terminal. Then, because this library doesn’t come with a predefined version of bootstrap, you’ll need to install bootstrap as well and add a CDN link in the index.js file, like below:

npm install boostrap

Now in the App.js file, add the following code to start creating the page layout: 

If you save and reload the page, your app should now look like this: 


Step 2: Create the Form.js component in the src folder#


Let’s start building the form component. In the src folder, create a new file called Form.js and add the following code: 

Next, replace the comment with the form fields: 

At the moment this isn’t a controlled component, as we’re not using state or props. So let’s add that to the form. First, add the constructor right under the SignupForm class.

Then, add the props to the form fields: 

We’re doing two things here: 

  • passing the value as a prop with this.state.name and this.state.email, and

  • adding the onChange event with the callback function that will update the state when the input value changes

So we now need to define the onChange functions, and we’ll do this right under the constructor: 

If you reload the page and add something in the name field, you’ll get this error: 


That’s because we have to bind the updateName and updateEmail functions in the constructor, like this: 

So now if you refresh again and inspect the form, you should see the input value changing as you type: 


Let’s add a checkbox to the form, to get the user’s consent for contacting them when this app launches. 

To align the checkbox to the rest of the input fields, I’ve used an offset of 2 columns. 

Next, we need to initiate the value of this checkbox in the state object. We’ll assign it a boolean value, which will be false by default. 

Now let's add a handler that will update the state on change.

Finally, we need to add a handler for submitting the form. The handleSubmit callback function will be called when the user clicks on the submit button.

We've added event.preventDefault() because we don't want to redirect the user to a new page when the form is submitted.

Let's not forget to bind this function in the constructor as well:

If you now reload the page and submit the form, you should see the alert message displayed:


Step 4: Validate the fields and show errors in real-time #


Before submitting the form, we want to validate the input fields to make sure they're not empty. This will happen purely on the client-side, so in the browser, not on the server-side.

By doing so, we can display error messages in real-time, to let the user know that the values added in the input fields aren't meeting the validation criteria.

To do the validation, we'll add another property to the state, called touched, which will keep track of the fields that are changed by the user. Initially, none of the fields have been touched by the user, so there's no point to validate anything.

We'll therefore initiate both the name and the email values here as false:

We want to know when the user interacts with these fields, so that we can run the validation function. So we need to add a new handler called handleBlur, which will update the touched values if the fields are touched:

So now every time the user interacts with the name or e-mail fields, the touched value is set to true.

Finally, let's make sure that the name field isn't empty, and that the e-mail address includes an "@" character.

We'll define a new function called validateFields, as follows:

So we're first checking if the name field was touched. If it was, and the length of the string typed by the user is lower than 2, then we're displaying an error message.

Next, let's add the e-mail field validation and return the errorvalue:

This function needs to be invoked in the render method, because we want to validate whenever there's a change in the input fields. So basically whenever the form is rendered again.

So let's add the validation function to the form, right under render:

To display the error messages, we need to add some new elements to the form.

To know if the fields have been touched, we need to add the handleBlur function to both name and email, like this:

Don't forget to bind the handleBlur function in the constructor, as we did with all the previous functions.

Let's test our validation! If we refresh the form, we should see our error messages in real-time:


Step 5: Send the form on submit#


Before sending the form, let's initiate a git repository and push all our changes to GitHub. You can access the repository here.

The last step in this tutorial is to send the form when the submit button is pressed. We'll use axios for this, so let's first install it by typing npm install axios -save in the terminal.

To make use of it, go to your Form.js component and import it.

Next, let's rework our handleSubmit function to make a post request when the form is submitted. We'll use the json.placeholder.com API for this, and we'll send the state object.

Now if you reload the page and submit the form, you should see the response logged in the console.

That's it, now all you have to do is replace the post endpoint with your actual backend endpoint and you're good to go.


Step-by-Step Tutorial: React Form With Uncontrolled Components #


For the uncontrolled form, we'll use a sandbox. You can find the final exercise here.

Step 1: Create the form component#

Let's first create the form component, in the App.js file:

Next, we'll initiate the constructor and bind the handleSubmit function, then we'll add the handler to the form.


Step 2: Add refs to retrieve the input values#

For the input fields, we mentioned that we have to use refs to retrieve the value. First, let's add them to the constructor:

Next, we'll add the refs to the form controls:

Let's check if this works correctly by displaying an alert message with the name and email value when the form is submitted.

If you now submit the form, it will display the two values:

All works well, so let's style the form a little.


Step 3: Style the form with Boostrap#

I'll add react-bootstrap and bootstrap as dependencies to the project.

We also need to import the Bootstrap styles into the App.js file, like we did in the previous example, and to redo the form fields as follows:

Now let's add the image and refresh the project.

Your landing page should look like this:

Step 4: Handle the checkbox and submit the form#

Next, we need to handle the checkbox. At the moment, it defaults to false, so if we add this value to the alert message, we see that it doesn't change when the checkbox is checked.

To handle this, we need to add another ref. First to the constructor, then to the form input field.

To test if it works correctly, let's display the value of the checkbox as an alert message when the form is submitted.

The form works correctly.

Finally, let's post this to the same API endpoint that we used for the previous form.

That's it! You now know how to create React controlled and uncontrolled forms.


Conclusion#

As you can see, creating uncontrolled forms requires less code and is easier, but the controlled components offer more flexibility and a better user experience.

More Resources#

If you'd like to continue learning React, check out the: