Form Handling in React.Js

Form Handling in React.Js

With any application, handling forms is a crucial part as we need to allow users to input and submit various information for processing. The normal way of dealing with forms in HTML such as using text inputs, checkboxes, selects, radio buttons, and so on remains true even with React.Js.

In React, you can either choose to allow the browser to handle most of the form elements and collect data through React change events, or you can use React to fully control the elements by setting and updating the input value directly. The first approach is called an uncontrolled component because React is not setting the value. The second approach is called a controlled component because React is actively updating the input.

In this article, we will check the two ways of handling forms in a React.Js application, how to submit a form (e.g. callback handler), and how to reset a form (e.g. after submission). By the end of this tutorial, you’ll be able to make a variety of forms using text inputs, checkboxes, select lists, and more.

Prerequisite

  • Have a development environment running Node.js

  • A React development environment set up with Create React App.

  • Basic knowledge of React such as how to manage state using useState, how to create components, etc.

  • You will also need a basic knowledge of JavaScript and HTML. We will also be using Tailwind CSS for our styling and basic knowledge of the framework would be beneficial. However, you don't need to know Tailwind CSS as all the styling is provided.

  • Click Here to get the starter code. The code to the complete demo used in this article is provided at the end of the article.

REACT FORM BY EXAMPLE

A common example of a form in various web applications is a login form where we capture both the email and password of the user we want to authenticate. A functional representation of such a form is as shown below

import React from "react";

const Form = () => {
    return (
        <form className="form">
            <div className="form-group">
               <label htmlFor="email">Email</label>
               <input className="form-input" type="email" />
            </div>
            <div className="form-group">
               <label htmlFor="password">Password</label>
               <input className="form-input"  type="password" />
            </div>
            <button className="form-button">Submit</button>
        </form>
    );
};

export default Form;

If you click submit button, the form will reload. This is the default behavior of a form that we should prevent in our form. We need to handle the submit event inside the component

To handle the event, we’ll add an event handler to the <form> element, not the <button>. Create a function called handleSubmit that will take the SyntheticEvent as an argument. TheSyntheticEvent is a wrapper around the standard Event object and contains the same interface. Call .preventDefault to stop the page from submitting the form then trigger an alert to show that the form was submitted:

import React from "react";

const Form = () => {
    const handleSubmit = (event) => {
        event.preventDefault();
        alert("You have submitted the form.");
    };

    return (
        <form className="form" onSubmit={handleSubmit}>
            <h4>Login</h4>
            <div className="form-group">
                <label className="form-label">
                    Email
                </label>
                <input className="form-input" type="email" />
            </div>
            <div className="form-group">
                <label className="form-label">
                    Password
                </label>
                <input className="form-input"  type="password" />
            </div>
            <button className="form-button">Submit</button>
        </form>
    );
};

export default Form;

Collecting Form Data Using Controlled Components

In the form above, we are rendering a simple form having two inputs one for email and the other for password. Nothing special. It is just like our regular HTML input. But to handle this input in React, we will need to understand the concept of a controlled input.

Uncontrolled and Controlled Input

In a React application, an "uncontrolled input" is an input field that is not associated with a React component state. This means that the value of the input is not managed by the React component, and the component does not have direct access to the value of the input.

On the other hand, a "controlled input" is an input field that is associated with a React component state. The value of the input is managed by the React component, and the component has direct access to the value of the input. This means that the component can programmatically control the value of the input, as well as respond to changes in the input value.

We will use the controlled way to handle the input's state. The benefit of using controlled input is that we are making the component state the single source of truth for the inputs. All we have to do is declare a state object where data will live.

const [email, setEmail] = useState("") // Set default value to ""

Now, for us to make the input field a controlled input, we assigned the state variable (which contains a default empty string) to the value prop.

<input className="form-input" value={email} type="text" />

After adding the value prop to input and assigning it to the email value from the state, if you try to write anything in the email text field, nothing will happen. This is because the value prop is assigned a state variable whose value is set to an empty string. And this is being forced on the input.

This is good because we now have total control over the input state. Let’s go ahead and update it. Update the code to include an onChange event handler.

const handleChange = e => {
    setEmail(e.target.value)
  }
...

<input className="form-input" value={email} onChange={handleChange} id="email" type="text" />

React needs an onChange handler to keep track of any changes in the field. Anytime you write something in the input field, this onChange event will trigger and then call its handleChange function that will re-render the state using setEmail function.

At this point, we have a controlled email input field where its state is being managed by its component.

Handling Multiple Inputs

In reality, you’ll be working with multiple input fields in your React application. Our example form has two inputs for email and password. In this scenario, we will make a simple adjustment not only to the handler function but also to the input element and the state.

We could decide to set up another useState Hook for the password input. Then go ahead and assign its state variable to the value prop. But this approach will require us to define another handler function to update the input state.

When we need to handle multiple inputs, we don’t want to make a new onChange handler function for each input. Therefore, we want to make a function that can set all values.

We can do that as follows:

const [state, setState] = useState({
    email: "",
    password: "",
  })

const handleChange = (e)=> {
    setState({
        ...state,  
        [e.target.name]: e.target.value
    });
}

NB: Our form elements must have a name property with a value matching the key for the elements state. For example, the email input must have name="email" since we are using the key email on the state to store the value for that input and the password input must have name="password".

Our input will change to be:

...
# Email Input
<input
    className="form-input"
    type="email"
    name="email"
    value={state.email}
    onChange={handleChange}
/>

...
# Password Input
<input
    className="form-input"
    type="password"
    name="password"
    value={state.password}
    onChange={handleChange}
/>

What is happening?

First, you will notice a significant change in the code. We started by modifying the useState Hook to include an additional input data. From there, we have access to the email and password through state.emailand state.passwordas used in the value prop of their respective input element.

In these input elements, we’ve added a name prop that holds also their respective state name (i.e emailand password). This is very important.

import React, { useState } from "react";

const Form = () => {
    const [state, setState] = useState({
        email: "",
        password: "",
    });

    const handleChange = (e) => {
        setState((prev) => ({
            ...prev,
            [e.target.name]: e.target.value,
        }));
    };
    const handleSubmit = (event) => {
        event.preventDefault();
        alert("You have submitted the form.");
    };

    return (
        <form className="form" onSubmit={handleSubmit}>
            <h4>Login</h4>
            <div className="form-group">
                <label className="form-label">
                    Email
                </label>
                <input
                    className="form-input"
                    type="email"
                    name="email"
                    value={state.email}
                    onChange={handleChange}
                />
            </div>
            <div className="form-group">
                <label className="form-label">
                    Password
                </label>
                <input
                    className="form-input"
                    type="password"
                    name="password"
                    value={state.password}
                    onChange={handleChange}
                />
            </div>
            <button className="form-button">Submit</button>
        </form>
    );
};

export default Form;

Now, let’s focus on the handleChange function. Here, we are using the setState function to update the inputs state.

const handleChange = (e)=> {
    setState({
        ...state,  
        [e.target.name]: e.target.value
    });
}

In this function, we are simply assigning to the element that is being targeted (through [e.target.name]) their corresponding values.

Still on the handleChange function. Anytime we group related data as we have it in the state variable, the state returned by the useState hook is not merged with that of the update passed to it. In other words, the useState Hook doesn’t merge the old and new state. Instead, it overrides the entire state with that of the current. So to avoid this scenario, we merge them by spreading the entire state object using the three dots before the state and overriding the part of it.

Now that you know how the control field works in React, adding any other input fields to a form will be a piece of cake. We need to update the state object to include the property for the new input, add the input, eg textarea to our form and add the name, value, and onChange properties to it similar to how we added them to the email and password input. Let's check some examples of other inputs.

The TextArea Input Field

In React, the textarea is defined as a self-closing element just like the input element.

As expected, we will have the state manage the user’s input (i.e textarea message). So, update the state to include a message property like so:

const [state, setState] = useState({
        email: "",
        password: "",
        message: ""
});

Next, add a textarea element in the form like so:

...
return (
    <form className="form" onSubmit={handleSubmit}>
            ...
            <div className="form-group">
                <label className="form-label">
                    Message
                </label>
                <textarea
                    className="form-input"
                    name="message"
                    value={state.message}
                    onChange={handleChange}
                />
            </div>
            <button className="form-button">Submit</button>
        </form>
)

Take note of the value and name prop in the textarea element. Just like the input field, the string assigned to the name prop must be the same as what we declared in the state object.

The Select Input Field

This is not different from the other input fields. As usual, we can make it a controlled input by first having the state manage the input data. Then add a value prop to the element and finally update it through the onChange handler function (but in our case, we don’t have to do anything here because we have the logic set already).

And don’t forget to add a name prop (to the element) that matches the name in the state. So let’s create a dropdown list with options to select car brands.

As expected, add a new property in the state. In my case, I will call it carBrand.

const [state, setState] = useState({
  ...
  carBrand: "",
});

Then, add the select element just before the closing </form> tag:

...
return (
    <form className="form" onSubmit={handleSubmit}>
            ...
            <div className="form-group">
                <label className="form-label">
                    Favorite Car Brand
                </label>
                <select
                    className="form-input"
                    name="carBrand"
                    value={state.carBrand}
                    onChange={handleChange}
                >
                    <option value="mercedes">Mercedes</option>
                    <option value="bmw">BMW</option>
                    <option value="audi">Audi</option>
                </select>
            </div>
            <button className="form-button">Submit</button>
        </form>
)

The Checkbox Input Field

Unlike the other input fields, the checkbox uses a checked prop (which is a Boolean attribute) instead of the value prop. The idea is that a checkbox is either checked or not.

We will need to adjust the handler function to accommodate the checkbox type of input. Ok, let’s start by adding a new property to the state. We can call it isChecked.

const [state, setState] = useState({
  ...
  isChecked: false,
});

Here, we assign a Boolean value of false so that the input field is unchecked by default. Next, add input checkbox just before the closing </form> tag.

...
return (
    <form className="form" onSubmit={handleSubmit}>
            ...
            <div className="form-group">
                <label className="form-label">
                    <input
                        type="checkbox"
                        className="form-input"
                        name="isChecked"
                        checked={state.isChecked}
                        onChange={handleChange}
                    />
                    Remember Me?
                </label>
            </div>
            <button className="form-button">Submit</button>
        </form>
)

Finally, update the handleChange function so you have:

const handleChange = e => {
   const value = e.target.type === "checkbox" ? e.target.checked : e.target.value; 
    setState({
        ...state,
        [e.target.name]: value
    })
}

In this function, we cannot use the earlier logic to manage the checkbox because it doesn’t have the value but checked attribute. So you’d need to adjust it if you want the same handleChange to manage the checkbox.

As seen in the handler, we now target the type and the checked attribute from this event parameter, e. From there, we are using the ternary operator, which is an inline if-statement to check the input types and then assign their corresponding value (either Boolean e.target.checked for the checkbox or e.target.value for every other input types).

The Radio Input Field

The radio input types combine the input text and the checkbox type. In other words, they use both the value and the checked prop.

We will create radio inputs that allow users to select gender. As expected, let’s add that to the state.

const [state, setState] = useState({
  ...
  gender: "",
});

Then, add the radio inputs just before the closing </form> tag:

...
return (
    <form className="form" onSubmit={handleSubmit}>
            ...
            <div className="form-group">
                <label className="form-label">
                    <input
                        type="radio"
                        className="form-input"
                        name="gender"
                        value="male"
                        checked={state.gender === "male"}
                        onChange={handleChange}
                    />
                    Male
                </label>
                <label className="form-label">
                    <input
                        type="radio"
                        className="form-input"
                        name="gender"
                        value="female"
                        checked={state.gender === "female"}
                        onChange={handleChange}
                    />
                    Female
                </label>
            </div>
            <button className="form-button">Submit</button>
        </form>
)

What’s happening?

As you know already, once you have the state manage your input, you immediately assign the state property to the name prop of the input. You should know from HTML that radio group share the same name. This allows us to select only one button at a time.

Notice that the value prop in these inputs are static unlike that of text inputs where its value comes from the state.

And finally, with the checked prop, we are saying that if the condition assigned is true, that radio button should be checked.

Simple as that!

While working with inputs in a React application, these are the most popular and all that we cover in this article.

Conclusion

We should handle input changes by writing controlled components. To do this, we attach an event handler function to the onChange event.

To handle form submission, we attach an onSubmit event handler to the form, and then get the event an object from the parameter and call event.preventDefault inside so we can run JavaScript code in the handler.

To handle multiple input changes with one onChange handler, we get the name and value properties from event.target from the handler’s parameter and update it with the dynamic property name feature available since ES6.

Source Code => https://github.com/marville001/form-handling-react

Be on the lookout for part 2 of this blog covering form validation using libraries such as react-hook-form and formik.

That's all for this article.

Thank you :)