DEV Community

Joseph Louie
Joseph Louie

Posted on

Controlled Form Inputs using React hooks

Why do you need Controlled Forms?

One reason why someone might use controlled inputs is to validate the input before submission.

The overall process of now controlled inputs work

  • user types -> calls handleChange -> sets the data based on the name

NOTE: setName/setUsername/etc are async

An example of how to implement controlled inputs in react hooks

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

export default function App() {
  // Where our state is being stored
  const [name, setName] = useState("");
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  // Everytime input changes we update the state
  const handleChange = (e) => {
    if (e.target.name === "name") {
      setName(e.target.value);
    } else if (e.target.name === "username") {
      setUsername(e.target.value);
    } else if (e.target.name === "email") {
      setEmail(e.target.value);
    } else if (e.target.name === "password") {
      setPassword(e.target.value);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("name: ", name)
    console.log("username: ",username)
    console.log("email: ",email)
    console.log("password: ",password)
  }

  // The value will be based on the state
  return (
    <div className="App">
      <h1>Controlled input</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>name</label>
          <input
            type="text"
            name="name"
            value={name}
            onChange={handleChange}
          ></input>
        </div>
        <div>
          <label>username</label>
          <input
            type="text"
            name="username"
            value={username}
            onChange={handleChange}
          ></input>
        </div>
        <div>
          <label>email</label>
          <input
            type="email"
            name="email"
            value={email}
            onChange={handleChange}
          ></input>
        </div>
        <div>
          <label>password</label>
          <input
            type="password"
            name="password"
            value={password}
            onChange={handleChange}
          ></input>
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Thank You for reading! I know that the part where we update the state can be improved to be more DRY, but I just find that the if-else statements make it easy to understand. If you have a better way to implementing that part let me know in the comments. This is usually what I would use when I implement a controlled form input.

Top comments (5)

Collapse
 
devhammed profile image
Hammed Oyedele • Edited

We can do better using an object, spread operator and computed properties instead of bunch of ifs.

This approach is more scalable and even allow sending the object as JSON easily.

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

export default function App() {
  // Where our state is being stored
  const [form, setForm] = useState({
    name: '',
    username: '',
    email: '',
    password: ''
  })

  // Everytime input changes we update the state
  const handleChange = (e) => {
    setForm(prevForm => ({
       ...prevForm,
       [e.target.name]: e.target.value
    }))
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('form: ', form)
  }

  // The value will be based on the state
  return (
    <div className="App">
      <h1>Controlled input</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>name</label>
          <input
            type="text"
            name="name"
            value={form.name}
            onChange={handleChange}
          ></input>
        </div>
        <div>
          <label>username</label>
          <input
            type="text"
            name="username"
            value={form.username}
            onChange={handleChange}
          ></input>
        </div>
        <div>
          <label>email</label>
          <input
            type="email"
            name="email"
            value={form.email}
            onChange={handleChange}
          ></input>
        </div>
        <div>
          <label>password</label>
          <input
            type="password"
            name="password"
            value={form.password}
            onChange={handleChange}
          ></input>
        </div>
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jolouie7 profile image
Joseph Louie

Yea, my approach wasn't the best. I was justing so used to seeing my state separated like that, that I just kept doing it. Your code is so much better and much more scaleable! Thank You for sharing! I'll be doing this from now on!

Collapse
 
sabbin profile image
Sabin Pandelovitch • Edited

Why do you use different state objects for each input?

This part of your code

const [name, setName] = useState("");
  const [username, setUsername] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  // Everytime input changes we update the state
  const handleChange = (e) => {
    if (e.target.name === "name") {
      setName(e.target.value);
    } else if (e.target.name === "username") {
      setUsername(e.target.value);
    } else if (e.target.name === "email") {
      setEmail(e.target.value);
    } else if (e.target.name === "password") {
      setPassword(e.target.value);
    }
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("name: ", name)
    console.log("username: ",username)
    console.log("email: ",email)
    console.log("password: ",password)
  }
Enter fullscreen mode Exit fullscreen mode

Can be simplified to this

const [formData, setFormData] = useState({
    name:'',
    username: '',
    email: '',
    password: ''
});

const handleChange = ({ target:{ name, value }}) => {
    setFormData(prevData => ({
      ...prevData,
      [name]: value,
    }))
};

const handleSubmit = (e) => {
    e.preventDefault();
    console.log(formData)
};
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jolouie7 profile image
Joseph Louie

Your right, my code isn't the best. Thank you for sharing how you would do it! I've near seen someone do this before. Pretty cool

{ target:{ name, value }}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sabbin profile image
Sabin Pandelovitch • Edited

This is a feature of ES6, called destructuring.

const obj = { a: 10, b: 15, child: { c:10, d:11}};

const { a, b } = obj; // this will create 2 variables a and b with the value from object.a object.b
const { a: foo, b: bar } = obj; // this will create 2 variables foo which is a, and bar which is b. This is actually for renaming the variables so you don't have conflicts in case you have the same defined before

const { a, child: { c } } = obj; // It can work on nested objects too, in this case you get a and c variables
Enter fullscreen mode Exit fullscreen mode

So actually it can be used on function parameters as well

const obj = { a: 10, b: 15, child: { c:10, d:11}};

const fn = param => console.log(param.a, param.child.c);
//param can be deconstructed 
const fn = ({ a, child: { c }) => console.log(a, c);
Enter fullscreen mode Exit fullscreen mode

There is more to this than the examples above.

Documentation -> developer.mozilla.org/en-US/docs/W...