Creating forms in React

Emmanuel AmooEmmanuel Amoo
12 min read

Introduction

Forms are essential components of the web, whether created with just HTML or with frameworks like React. Forms allow users to send data, perform basic tasks on the internet such as posting a tweet, sending a message, or online shopping, and also retrieve data. Forms are the web's foundation, and networking would not be possible without them.

Creating effectual forms is a necessity for developers because it allows them to collect data from users in a way that is efficient and easy to understand. When forms are well-designed, they can help developers avoid errors and ensure they are collecting the data they need. Additionally, effective forms can help improve the user experience by making it easy for users to provide the needed information.

Unlike HTML forms, React allows for the dynamic handling of form data. When working with forms in HTML or vanilla Javascript, it's essential to remember that the inputs can only be processed after submitting the form. This means that any changes made by the user won't be monitored until the very end of the process. However, in React, we have the ability to create states that can track every change made during the user's interaction with the form. This is a powerful tool that can significantly improve the user experience and make the process much more efficient. Information processing starts as soon as the user begins interacting with the form. This means that after submitting the form, we submit the data and pass the state that we have been tracking all along.

React presents two ways to handle data in forms: The first way is through uncontrolled components, which allow the form elements and data to be handled by the DOM. The second way is through controlled components, where the data is fully handled by React through hooks.

Prerequisites

  • Proficiency in HTML, particularly HTML forms and form elements, and CSS.

  • Knowledge of Javascript

  • Knowledge of the DOM

  • Basic knowledge of React and useState hook

  • A text editor for writing code

Creating Forms Through Uncontrolled Components

In uncontrolled components, the state of form elements is not managed by React, but rather by the DOM. When users input text into form fields in uncontrolled components, the browser controls the state of those elements and updates the DOM for that element.

Here, no value property is set for form elements, instead, we use the onChange event handler to receive users' input. Moving on in this tutorial, we will:

  • Create basic forms

  • Manage the state of our input with the useState hook

Creating Simple Forms

First, create a Form.js file and import react and useState

import React, { useState } from "react"

Then, export a default Form function, returning a form element within it

export default function Form () {
    return (
        <form>

        </form>
    )
}

Then, create an input element inside the form with a type and placeholder of First name and an onChange event while passing in a handleChange function. Like below

import React, { useState } from "react"

export default function Form () {
    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
        </form>
    )
}

So, let's handle changes in the First name input using the useState hook. Let’s just create a handleChange function and console.log to check for changes in our input.

The handleChange function must be nested inside the form component like below

import React, { useState } from "react"

export default function Form () {

    function handleChange() {
        console.log("Changed!")
    }

    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
        </form>
    )
}

So, we can test and see that as we type into our input field, Changed! is printed to the console

Next, we use let to declare a firstName variable and console.log firstName to see the changes in the user’s input as they type

import React, { useState } from "react"

export default function Form () {

    let firstName

    console.log(firstName)


    function handleChange() {
        console.log("Changed!")
    }

    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
        </form>
    )
}

In our onClick handler function, we pass event to our function, then we remove the console.log there, replacing it with the code

function handleChange(event) {
        firstName = event.target.value
    }

The code would appear as follows

import React, { useState } from "react"

export default function Form () {

    let firstName

    console.log(firstName)


    function handleChange(event) {
        firstName = event.target.value
    }

    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
        </form>
    )
}

Let's execute the file and see the output in the console

We can see that despite typing repeatedly, nothing happens in our console because React cannot detect changes in firstName, so we use the useState hook to declare our variable and set a state to allow for changes in firstName.

First, we declare an array of firstName and setFirstName equal to useState which is set to an empty string

    const [firstName, setFirstName] = useState("")

Next, we pass an event object from the onClick event handler to our handleClick function, and then we replace the console.log to update the firstName state on every keystroke

function handleChange(event) {
        setFirstName(event.target.value)
    }

Then add a console.log in the body of the Form function to see the changes in user input as they type

import React, { useState } from "react"

export default function Form () {

    const [firstName, setFirstName] = useState("")

    console.log(firstName)


    function handleChange(event) {
        setFirstName(event.target.value)
    }

    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
        </form>
    )
}

We can see that, unlike the previous attempt, React is able to detect changes in its input and update firstName accordingly.

Adding another input field to the form

We can add another input field to the form in two ways and we’ll explore both ways in this section

A. Duplicating Our Former Code By Adding Another Hook

By duplicating our former code and editing it accurately, we can add another input field, and track and update its state in React as we formerly did.

First, we create another input field with an onChange event listener with a handleSurName function

<input type="text" placeholder="surName" onchange={handleSurName} />

We create our surName state variable with the useState hook

    const [surName, setSurName] = useState("")

The next step is to create the handleSurName function using the handleChange function as a model

import React, { useState } from "react"

export default function Form () {

    const [firstName, setFirstName] = useState("")
    const [surName, setSurName] = useState("")


    function handleChange(event) {
        setFirstName(event.target.value)
    }

    function handleSurName(event) {
        setSurName(event.target.value)
    }


    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
            <input type="text" placeholder="surName" onchange={handleSurName} />

        </form>
    )
}

Then console.log the firstName and surName to allow us to see the output

However, if we have more inputs, we will duplicate more code, leading to poor-quality code that may make it difficult to maintain. In the next section, we will learn how to do the same thing in a cleaner and more acceptable format.

B. Combining State into Object

By combining states, we can create and maintain a single state to manage multiple inputs. This allows us to use a single state to control and manage multiple inputs, checkboxes, and radio elements. It gives us greater control, simplifies the code, and improves code quality, especially if the code is to be used by other developers.

First, we the duplicate handleSurName and state variable and replace the placeholder surName with lastName since that's easily understandable, and we pass handleChange to it

import React, { useState } from "react"

export default function Form () {

    const [firstName, setFirstName] = useState("")


    function handleChange(event) {
    }


    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
            <input type="text" placeholder="lastName" onchange={handleChange} />

        </form>
    )
}

To create a single state that can be used by all types of input, we need to create an object in the state. A state variable can accept multiple data types, so we create the object in the state. We need to change the name of the variable from firstName to something more generic, like formFields

import React, { useState } from "react"

export default function Form () {

    const [formFields, setFormFields] = useState({
        firstName: "",
        lastName: ""
    })


    function handleChange(event) {
    }


    return (
        <form>
            <input type="text" placeholder="First Name" onChange={handleChange} />
            <input type="text" placeholder="lastName" onchange={handleChange} />

        </form>
    )
}

Unfortunately, we cannot use event.target.value to get the data from the form fields because rather than giving us our data in object form, our object will be erased and the output will be a string, which is not helpful. Since we are using the same handleChange function on both inputs, we need a way to identify which input triggered the event. To do this, we give the inputs a name property that matches the property name that we are storing



          <input 
                type="text" 
                placeholder="First Name" 
                onChange={handleChange} 
                name="firstName" 
                />

            <input 
                type="text" 
                placeholder="Last Name" 
                onChange={handleChange} 
                name="lastName" 
                />

To check if our code is working, we’ll console.log event.target.name to check which input field is triggering the event listener. Let’s test our code

With this, we have everything we need to call formFields and update our object correctly. To do this, we call setFormFields to accept changes in state and update our formFields variable respectively. We’ll call setFormFields in our onChange function, and then we’ll pass our previous state (we didn’t do that with strings because overwriting our file doesn’t actually change anything) preventing outright overwriting of our earlier state, passing a callback function which will then return an object. The object takes in a copy of the previous state, then the specific property we want to change in the object is passed to it. This causes only the value of the property we want to change to be overwritten whilst preserving the other components of the object

import React, { useState } from "react"

export default function Form () {

    const [formFields, setFormFields] = useState({
        firstName: "",
        lastName: ""
    })

    function handleChange(event) {
        setFormFields(prevState => {
            return {
                ...prevState,
                [event.target.name]: event.target.value
            }
        })
    }

    return (
        <form>
            <input 
                type="text" 
                placeholder="First Name" 
                onChange={handleChange} 
                name="firstName" 
                />

            <input 
                type="text" 
                placeholder="Last Name" 
                onChange={handleChange} 
                name="lastName" 
                />
        </form>
    )
}

Computed properties was used to turn the dynamic string event.target.name and use it as the property name for the object. Console.log formFields to see our state as it changes

The object is initially empty but as we start typing, the object starts changing and our state remains intact.

Adding Another Input

Adding another input field will be very easy since we just have to add a new field to our state object rather than adding another event handler function and a new state.

Let’s demonstrate that by adding a Username field in our state object. Kindly ensure that the name of the property in the state object is the same as the one in the input tag

import React, { useState } from "react"

export default function Form () {

    const [formFields, setFormFields] = useState({
        firstName: "",
        lastName: "",
        userName: ""
    })


    function handleChange(event) {
        setFormFields(prevState => {
            return {
                ...prevState,
                [event.target.name]: event.target.value
            }
        })
    }


    return (
        <form>
            <input 
                type="text" 
                placeholder="First Name" 
                onChange={handleChange} 
                name="firstName" 
                />

            <input 
                type="text" 
                placeholder="Last Name" 
                onChange={handleChange} 
                name="lastName" 
                />

            <input 
                type="text" 
                placeholder="Username" 
                onChange={handleChange} 
                   name="userName" 
                  />

        </form>
    )
}

Let’s add favColour to the state object

const [formFields, setFormFields] = useState({
        firstName: "",
        lastName: "",
        userName: "",
        description: "",
        isTrue: true,
        tiers: "",
        favColour: ""
    })

Then, we add a name, value and the onChange event handler to the select element

<select 
                    id="favColour"
                    name="favColour"
                    value={formFields.favColour}
                    onChange={handleChange}
                    >

With those lines of code, the dropdown works as expected

However, the initial value of the select element is an empty string, but there is no option that represents the empty string. We can resolve that by creating an option element with an empty string value

<option value="">-- Choose --</option>

And this is the total code

import React, { useState } from "react"

export default function Form () {

    const [formFields, setFormFields] = useState({
        firstName: "",
        lastName: "",
        userName: "",
        description: "",
        isTrue: true,
        tiers: "",
        favColour: ""
    })


    function handleChange(event) {
        const {name, value, type, checked} = event.target
        setFormFields(prevState => {
            return {
                ...prevState,
                [name]: type === "checkbox" ? checked : value
            }
        })

    }


    return (
        <form>
            <input 
                type="text" 
                placeholder="First Name" 
                onChange={handleChange} 
                name="firstName" 
                value={formFields.firstName}
                />

            <input 
                type="text" 
                placeholder="Last Name" 
                onChange={handleChange} 
                name="lastName" 
                value={formFields.lastName}
                />

            <input 
                type="text" 
                placeholder="Username" 
                onChange={handleChange} 
                name="userName" 
                value={formFields.userName}
                />

            <textarea 
                placeholder="description"
                onChange={handleChange}
                name="description"
                value={formFields.description}
                />

            <input
                type="checkbox"
                id="isTrue"
                checked={formFields.isTrue}
                onChange={handleChange}
                name="isTrue"
                    />
                <label htmlFor="isTrue">Subscribing</label>

                <select 
                    id="favColour"
                    name="favColour"
                    value={formFields.favColour}
                    onChange={handleChange}
                    >
                    <option value="">-- Choose --</option>
                    <option value="red">Red</option>
                    <option value="blue">Blue</option>
                    <option value="yellow">Yellow</option>
                    <option value="indigo">Indigo</option>
                    <option value="orange">Orange</option>
                    <option value="violet">Violet</option>

                </select>

        </form>
    )
}

Submitting Forms

In React, the button element is used to handle the submit. By default the button element is of type submit, so we’ll use this in our code

<button>Submit</button>

Clicking the button will cause the form to trigger the onSubmit event handler. So, we add the onSubmit event handler prop to our form with the handleSubmit function which we’ll create

<form onSubmit={handleSubmit}>

Next, create the handleSubmit function and pass the event to it. In the body of the function, run event.preventDefault to prevent the page from refreshing, causing React to rerender the component

function handleSubmit(event) {
        event.preventDefault()
    }

This is the whole code, you can style it way you want to

import React, { useState } from "react"



export default function Form () {
    const [formFields, setFormFields] = useState({
        firstName: "",
        lastName: "",
        userName: "",
        description: "",
        isTrue: true,
        tiers: "",
        favColour: ""
    })


    function handleChange(event) {
        const {name, value, type, checked} = event.target
        setFormFields(prevState => {
            return {
                ...prevState,
                [name]: type === "checkbox" ? checked : value
            }
        })
    }

    function handleSubmit(event) {
        event.preventDefault()
    }

    return (
        <form onSubmit={handleSubmit}>
            <input type="text" 
            placeholder="First Name" 
            onChange={handleChange} 
            name="firstName" 
            value={formFields.firstName}
            />

            <input type="text" 
            placeholder="Last Name" 
            onChange={handleChange} 
            name="lastName" 
            value={formFields.lastName}
            />

            <input type="text" 
            placeholder="Username" 
            onChange={handleChange} 
            name="userName" 
            value={formFields.userName}
            />

            <textarea 
                placeholder="description"
                onChange={handleChange}
                name="description"
                value={formFields.description}
                />

              <input
                    type="checkbox"
                    id="isTrue"
                    checked={formFields.isTrue}
                    onChange={handleChange}
                    name="isTrue"
                    />
                <label htmlFor="isTrue">Subscribing</label>
                <br />


                <fieldset>

                    <legend>Choose Subscription Plan</legend>

                    <input 
                        type="radio"
                        id="tier1"
                        name="tiers"
                        value="tier1"
                        onChange={handleChange}
                        checked={formFields.tiers === "tier1"}
                    />
                    <label htmlFor="tier1">Tier 1</label>
                    <br />
                    <input 
                        type="radio"
                        id="tier2"
                        name="tiers"
                        value="tier2"
                        onChange={handleChange}
                        checked={formFields.tiers === "tier2"}
                    />
                    <label htmlFor="tier2">Tier 2</label>
                    <br />
                    <input 
                        type="radio"
                        id="tier3"
                        name="tiers"
                        value="tier3"
                        onChange={handleChange}
                        checked={formFields.tiers === "tier3"}
                    />
                    <label htmlFor="tier3">Tier 3</label>
                    <br />
                </fieldset>
                <br />

                <label htmlFor="favColour">What is your favourite colour</label>
                <br />

                <select 
                    id="favColour"
                    name="favColour"
                    value={formFields.favColour}
                    onChange={handleChange}
                    >
                    <option value="">-- Choose --</option>
                    <option value="red">Red</option>
                    <option value="blue">Blue</option>
                    <option value="yellow">Yellow</option>
                    <option value="indigo">Indigo</option>
                    <option value="orange">Orange</option>
                    <option value="violet">Violet</option>

                </select>
                <br />
                <br />

                <button>Submit</button>

        </form>
    )
}

With that, we’ve come to the end of this how-to guide.

Conclusion

Congratulations! You can now create forms in React. To access the source code used in this tutorial, head over to the repository to get it.

6
Subscribe to my newsletter

Read articles from Emmanuel Amoo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Emmanuel Amoo
Emmanuel Amoo

I'm Emmanuel, a full-stack developer and technical writer. I love solving diverse problems especially ones that challenge the norms and drive my creativity. I specialize in writing Node.js and React applications. My hobbies are designing, exploring art and reading.