Form handling in React : the correct way

Introduction

Suppose you are building or planning to build an application using React. In that case, chances are high that you will create a form for your users to send data whether it is about login, registration, or a specific form in a particular need.

In this article, we will explore how to handle forms in React, in a simple way. There are a lot of libraries out there you can use for validation, form submitting, formatting, and much more. However, you need to understand the basics before moving on and using these tools.

We will talk about state management for form components, controlled inputs, uncontrolled inputs, and form events.

Let's set up a quick React project.

Setup project

yarn create react-app react-forms

This will create a simple React project that you can access in the react-forms directory.

Form handling

Before starting to manipulate the state in an HTML form, let's recap how forms work in HTML.

A form in HTML acts as a point of interaction between the user and the website. Ideally, a form is used to submit data to a server using controlled text such as input, select, radios, or any other HTML input that can accept any form of data.

In traditional web development with PHP for example, the data submitted to the form can be sent to the server and manipulated. For that, you can use the action to tell the website which file should handle the data submitted in the form.

<form action="process_form.php" method="post">
  First name:<br>
  <input type="text" name="firstname" value=""><br>
  Last name:<br>
  <input type="text" name="lastname" value=""><br><br>
  <button type="submit">Submit</button>
</form>

In the code above, when the form is submitted with a click on the submit button, the data entered on the First name and Last name input is sent to process_form.php.

0:00
/
Demo of a HTML form

As you can see in the video, when the form is submitted, the page is refreshed as the page is expecting data coming from the `process_form.php` script. Well, we are not doing PHP here, so now that you understand a bit more about forms, let's write a form in React.

Writing a form in React

Let's rewrite the form we build earlier in React. Nothing complicated to do here, we will write the same code in the App.js file.

import "./App.css";

function App() {
  return (
    <div className="App">
      <form action="" method="post">
        First name:
        <br />
        <input type="text" name="firstname" />
        <br />
        Last name:
        <br />
        <input type="text" name="lastname" />
        <br />
        <br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default App;

You will have something like this

Form in React

Great! In React and as you are working with JavaScript, you will need to retrieve the data from the form on the frontend and decide whatever you want to do with it. In this article, we will just retrieve the data from this form and construct a JavaScript object that can be sent to a server or used to do whatever the business logic wants.

We will create for each input, a state that will store the value of this input.

import React, { useState } from "react";
import "./App.css";

function App() {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault();

    console.log({
      firstName,
      lastName,
    });
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        First name:
        <br />
        <input
          type="text"
          name="firstname"
          value={firstName}
          onChange={(e) => setFirstName(e.target.value)}
        />
        <br />
        Last name:
        <br />
        <input
          type="text"
          name="lastname"
          value={lastName}
          onChange={(e) => setLastName(e.target.value)}
        />
        <br />
        <br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default App;

Okay. New lines have been added to this code but let's start with the input tags. Each input text has a value and a onChange function. The value props are linked to the respective state (lastName for the input Last name ) and the onChange the method is linked to the respective setState function.

What is interesting is the handleSubmit function which is a value of the onSubmit props on the form tag.

In the first line of the function, we can notice the following syntax.


...
    event.preventDefault();
...

If you remember at the beginning of the article with the demo with a form that sends data to PHP, once there is a click on the submit button, the page is refreshed. This is the default behavior of the submission of a form on a website. As we are building reactive applications, we don't want this behavior. So the event.preventDefault() will block the reloading of the page and we can continue our actions.

Eventually, you will be using the fetch API or axios to send data retrieved from the state to a remote server or API. But this is basically how forms are handled in React.

But there are some nuances you must understand to avoid some errors in form handling with React.

Controlled inputs and Uncontrolled inputs

In the precedent section, we used useState to track the values of the inputs. And the data is changed in these inputs, we also change the values of the state: we have built controlled inputs.

In React, an input is considered "controlled" if its value is controlled by the state of a React component. This is done by setting the value attribute of the input field to a state variable and then using an onChange handler to update that state variable every time the input field changes.

An input is called uncontrolled if the changes are directly handled by the DOM, and it is possible to do it in React, using the `useRef` hook that can help us access the element in the DOM directly. Interesting, right? Let's rewrite the form using useRef.

import React, { useRef } from "react";
import "./App.css";


function App() {
  const firstNameRef = useRef();
  const lastNameRef = useRef();

  const handleSubmit = (event) => {
    event.preventDefault();

    const firstName = firstNameRef.current.value;
    const lastName = lastNameRef.current.value;

    console.log({
      firstName,
      lastName,
    });
  };

  return (
    <div className="App">
      <form onSubmit={handleSubmit}>
        First name:
        <br />
        <input
          type="text"
          name="firstname"
          ref={firstNameRef}
        />
        <br />
        Last name:
        <br />
        <input
          type="text"
          name="lastname"
          ref={lastNameRef}
        />
        <br />
        <br />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default App;

We're using the useRef hook to create a ref for each input field, firstNameRef and lastNameRef.

We've assigned these refs to the ref attribute of the corresponding input fields.

In the handleSubmit function, we're accessing the current values of the input fields directly from the DOM using the refs. In our case, we are using firstNameRef.current.value and lastNameRef.current.value.

This makes the inputs uncontrolled components because their values are not being controlled by the state of the React component; instead, they're being retrieved directly from the DOM when the form is submitted.

All of this sounds great, but when to use controlled inputs or uncontrolled inputs? You can use controlled inputs if you are going to do some complex validations or if you need to enforce the input format. You can use uncontrolled inputs if you don't need validation on the form, and also if you are doing some integrations with a non-React code library.

And with this, we have made a go-through of how to work with forms in React. To avoid the hassle of writing your own validation rules, or input components, there is plenty of React libraries available to use that can make your development workflow faster and can also provide a better UX for your users.

Conclusion

In this article, we have learned how to properly build a form in React. We first learned how a form works in HTML and then how to build one in React. We have also learned the difference between controlled inputs and uncontrolled inputs and when to use each one.