How to Create Controlled Components

Adnan WazwazAdnan Wazwaz
7 min read

Introduction

If you have been learning React, then it's inevitable that you will run into the concept of controlled inputs. But what exactly is a controlled input or a controlled form? This article will cover all the essentials that pertain to this concept. You’ll also see the difference between how forms are managed in React versus such in vanilla JavaScript.

When a component, input, or form is “controlled”, it simply means that the input values tied to them are derived from state. In React, state is internal, dynamic data that the components have access to. There are times when we need to update the values of our variables, and unfortunately, we are unable to do so with props because props do not change. We can change the values of states inside our components at any time.

Why We Need Them?

Arguably, the main reason that you would need a controlled component is for form validation. Let’s say you had an input of text, but you wanted to filter out certain characters. Unlike with an uncontrolled component, you can easily do that using a controlled component and an event listener. As you physically change the values of the form elements, state is being set to those new values. State will update, even when you type one character in a text field. And this is NOT an exaggeration.

Creating Controlled Component

REVIEW: Uncontrolled Forms in Vanilla JavaScript

Firstly, it’s important to review how to create uncontrolled forms in vanilla JavaScript, so that you don’t lose your grasp of the building blocks. However, if you feel that you have a full grasp on it, then you may skip to the next section.

In uncontrolled forms, data is handled by the DOM. And it’s a lot of work to retrieve specific form elements and values, even when you’re using React. There is always the need to write the following types of lines of JS code:

document.getElementById('some-id').value; // One way to retrieve a value
document.querySelector('#some-id').value; // Another way to retrieve a value

/*
* Retrieving elements by id or name, inside event listeners.
* This would only work if the id or name is a single word not broken up by
* spaces or dashes. Otherwise, you would have to use methods like
* getElementbyId() or querySelector()
*/
event.target.id.value; 
event.target.name.value

As you are aware, JS code gets unbearably long fast, becoming difficult for other programmers to decipher. You would have multiple lines of code in one page that would look something like this:

<form id="my-form">

        <!-- Username field -->
        <label for="username">UserName: </label>
        <input id="username" name="username" type="text" />
        <br/>

        <!-- Password field -->
        <label for="password">Password: </label>
        <input id="password" name="password" type="password" />
        <br/>

        <!-- Submit-->
        <input type="submit" />
    </form>
 document.getElementById('my-form').addEventListener('submit', (e) => {
        e.preventDefault();

        // Retrieve values and login.
        const usernameElement = e.target.username;
        const passwordElement = e.target.password;
        login(usernameElement.value, passwordElement.value);

        // Clear fields.
        usernameElement.value = "";
        passwordElement.value = "";

    });

Luckily in this example, the JS code is only 13 lines. But imagine how long it would be if we had to make fetch requests to the server.

Make it Easier with React

In controlled React components, however, you wouldn’t have to make a mess like you would in plain old JavaScript. The code will be more secure and more readable, and the form values will be more easily accessible. What you would have to do is the import the React hook, useState and declare state variables for all our input values using useState. In our JavaScript example, we have a simple form that takes in a username and password. As we stick with this example, we will declare state for username and password.

The next thing to do is in your form, you would need to add a value attribute and set it to the state variable. At this point, by doing this, no changes will be made if you try to type anything into the text fields. Now the form is locked by state and the text in the UI will only change when the state values change.

This is where we apply the next step in this process afterwards. In order to get the state value to change programmatically, we need to append the event listener attribute on the input text fields, onChange. In this event listener, you would need to pass in the event object. (Call it “event”, “e”, or whatever you like.) You need to call the setter function for that specific state and pass in the new value that you wanted to change the state to (i.e. e.target.value). This way, you don’t even have to make the extra step to access the id or the name, using getElementbyId() or querySelector().

The last thing that you would have to do is add an onSubmit event listener attribute to the form element and set it to a callback function to handle form submission and validation. The final result would look like this:

import { useState} from "react";

function Form( {onLogin} ) {
    const [username, setUsername] = useState("");
    const [password, setPassword] = useState("");

    function handleSubmit(e) {
        e.preventDefault();
        onLogin(username, password);
    }

    return (
        <form id="my-form" onSubmit={handleSubmit}>

            {/* Username field */}
            <label for="username">UserName: </label>
            <input 
                id="username" name="username" type="text" value={username} onChange={(e) => setUsername(e.target.value)}
            />
            <br />

            {/* Username field */}
            <label for="password">Password: </label>
            <input id="password" name="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)}/>
            <br />

            {/* Submit */}
            <input type="submit" />
        </form>
    )
}

export default Form;

Even though it’s more lines of code, it’s still only secluded to one page. One thing that I should have mentioned earlier is that the states themselves, don’t have to be in the same component, but can be in a parent component. You can use the parent component to pass state values and helper methods as props to the child component, and can update state variables using the callback functions passed as props.

import { useState } from "react";
import Form from "./Form";

function Parent() {
    const [username, setUsername] = useState("");
    const [password, setPassword] = useState("");

    // Don't worry about security violations. It's all just for demonstrative purposes.
    function login(e) {
        e.preventDefault();
        console.log("logged in!");
        console.log(username);
        console.log(password);
    }

    function updateUsername(username) {
        setUsername(username);
    }

    function updatePassword(password) {
        setPassword(password);
    }

    return (
        <Form 
            username={username} 
            password={password} 
            onLogin={login} 
            onSetUsername={updateUsername}
            onSetPassword={updatePassword}
        />
    )
}

export default Parent;
function Form( {
    username,
    password,
    onLogin,
    onSetUsername,
    onSetPassword
} ) {

    return (
        <form id="my-form" onSubmit={onLogin}>

            {/* Username field */}
            <label for="username">UserName: </label>
            <input 
                id="username" name="username" type="text" value={username} onChange={onSetUsername}
            />
            <br />

            {/* Username field */}
            <label for="password">Password: </label>
            <input id="password" name="password" type="password" value={password} onChange={onSetPassword}/>
            <br />

            {/* Submit */}
            <input type="submit" />
        </form>
    )
}

export default Form;

Making it Even Easier

You’re probably thinking that it’s a hassle in of itself to declare a state variable for every input field. Because this would mean you have to write a unique callback function for each input field to handle its changes. So how can we make this easier for ourselves?

The answer is simple. Instead of declaring multiple state variables for the input fields, declare one state variable as an object. Its keys would be the id’s or names tied to the form elements.

With this in mind, you would only have to write one callback function that can be tied to all input fields. To update state, you would simply need to do the following:

  1. Call the setter function for the state.

  2. Copy the object using the spread operator.

  3. Using bracket notation, declare the key using e.target.name, and the value to be e.target.value.

import { useState } from "react";

function EasyForm( {onLogin} ) {
    const [formData, setFormData] = useState({
        username: "",
        password: ""
    });

    function handleChange(e) {
        setFormData({
            ...formData,
            [e.target.name]: e.target.value
        })
    }

    function handleSubmit(e) {
        e.preventDefault();
        onLogin(formData);
    }

    <form id="my-form" onSubmit={onLogin}>

            {/* Username field */}
            <label for="username">UserName: </label>
            <input 
                id="username" name="username" type="text" value={username} onChange={handleChange}
            />
            <br />

            {/* Username field */}
            <label for="password">Password: </label>
            <input id="password" name="password" type="password" value={password} onChange={handleChange}/>
            <br />

            {/* Submit */}
            <input type="submit" />
        </form>
}

One thing to note is that if you’re using check boxes, then you would have to call “checked” instead of value. You can use your JS knowledge to set up validations inside callback functions like handleChange().

Conclusion

And those are the basics for creating controlled components in React JS. I hope you found this article helpful. I linked a couple of other resources if you're interested in learning more. Until then, happy coding.

Useful Resources

ReactJS State: SetState, Props and State Explained

Controlled vs Uncontrolled Components in React

0
Subscribe to my newsletter

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

Written by

Adnan Wazwaz
Adnan Wazwaz