DEV Community

Debora Galeano
Debora Galeano

Posted on

How to handle multiple inputs in React

I recently had to refactor a React Form when working with multiple inputs and I thought I'd share how I did it.

This is how my form looks like:

Screen Shot 2020-11-28 at 10.38.42 PM

The Problem

  • Look at the starter code below
  • This Form component has 5 input fields in total; 5 different states and 5 different onChange inline functions
  • This is not exactly DRY code 🙀
import React, { useState } from "react";

export default function Form() {
  const [newCompany, setCompany] = useState("");
  const [newPosition, setPosition] = useState("");
  const [newLink, setNewLink] = useState("");
  const [newDate, setNewDate] = useState("");
  const [newNote, setNewNote] = useState("");

  return (
        <form>
          <input
            value={newCompany}
            onChange={(e) => setCompany(e.target.value)}
            label="Company"
          />
          <input
            value={newPosition}
            onChange={(e) => setPosition(e.target.value)}
            label="Job Title"
          />
          <input
            value={newLink}
            onChange={(e) => setNewLink(e.target.value)}
            label="Job Link"
          />
          <input
            type="date"
            value={newDate}
            onChange={(e) => setNewDate(e.target.value)}
          />
          <input
            value={newNote}
            onChange={(e) => setNewNote(e.target.value)}
            label="Note"
          />
          <button type="submit"> Submit </button>
        </form>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • Also, if I want to add a reset function later, my code will look like this: 🙅🏽‍♀️
  const reset = () => {
    setCompany("");
    setPosition("");
    setNewLink("");
    setNewDate("");
    setNewNote("");
  };
Enter fullscreen mode Exit fullscreen mode

The Solution: Refactoring ✨

Step 1: Add input default values and initialize state

  • First, let's add default values to ALL input fields
  • How do we do that? We create an object literal with those values and set to empty string
  • Then, with the useState() React Hook we initialize our values state with the initialValues object
  • Important: Remember to add the value attribute to every input field with its corresponding value (e.g. values={values.company})
const initialValues = {
  company: "",
  position: "",
  link: "",
  date: "",
  note: "",
};

export default function Form() {
  const [values, setValues] = useState(initialValues);

  return (
        <form>
          <input
            value={values.company}
            onChange={(e) => setCompany(e.target.value)}
            label="Company"
          />
 //...
Enter fullscreen mode Exit fullscreen mode

Step 2: Handle multiple input change

  • The goal here is to handle ALL inputs with a single onChange handler
  • In order to update and keep track of our input fields every time they change, we need to create a handleInputChange function (see below)
  • What's happening here? (quick recap)
    • First, we're using object destructuring to get or extract the name and the value attributes from our inputs (look at the the comments below - they're equivalent)
    • Then, we're updating our values state object with the existing values by using the setValues() function and the spread operator
    • And finally, we're updating the value of the event that was triggered by that onChange with this ES6 syntax: [name]: value
    • This is a very important step! We need to add a name attribute to our inputs and [name]: value here means that we're setting a dynamic name property key (taken from our inputs - e.g. company: e.target.value) which will be equal to the value of our current input state.

Reference: React Docs

 //... 

  const handleInputChange = (e) => {
    //const name = e.target.name 
    //const value = e.target.value 
    const { name, value } = e.target;

    setValues({
      ...values,
      [name]: value,
    });
  };

  return (
        <form>
          <input
            value={values.company}
            onChange={handleInputChange}
            name="company" //IMPORTANT 
            label="Company"
          />
   // ... 
Enter fullscreen mode Exit fullscreen mode

Step 3: Add handleInputChange to input fields

  • Add the handleInputChange function to the onChange prop of every input field
  • Look at the final code; this is a much cleaner and manageable form 👌🏽
import React, { useState } from "react";

const initialValues = {
  company: "",
  position: "",
  link: "",
  date: "",
  note: "",
};

export default function Form() {
  const [values, setValues] = useState(initialValues);

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setValues({
      ...values,
      [name]: value,
    });
  };

  return (
        <form>
          <input
            value={values.company}
            onChange={handleInputChange}
            name="company"
            label="Company"
          />
          <input
            value={values.position}
            onChange={handleInputChange}
            name="position"
            label="Job Title"
          />
           // ... Rest of the input fields
          <button type="submit"> Submit </button>
        </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

I hope it was useful. All comments and feedback are welcome! 🎊

Discussion (18)

Collapse
donchocska profile image
donchocska

Amazing perofrmance. Very thanks!

Collapse
musawershah1598 profile image
Musawer Shah

Hi this method works fine for multiple input but just with normal input fields. What if their are some booleans and arrays??? E.g In vue.js I have the following data model which change at some point in time, which in that case two way data binding make it way easier for us.

data() {
        return {
            valid: false,
            showDialog: true,
            showFullLoading: false,
            isImage: false,
            product: {
                name_en: "",
                name_ar: "",
                category: "",
                subcategory: "",
                description_en: "",
                description_ar: "",
                meta_title: "",
                meta_description: "",
                meta_keywords: "",
                price: 0.0,
                showSale: false,
                sale: 0.0,
                image: "",
                saleAfterStock: false,
                stock: 10
            },
            images: [
                { image: null, isImage: false, id: shortid.generate() },
                { image: null, isImage: false, id: shortid.generate() },
                { image: null, isImage: false, id: shortid.generate() },
                { image: null, isImage: false, id: shortid.generate() }
            ],
            attributes: [],
            defaultVariants: [],
            dataWeightClass: ["Gram", "Kilo Gram", "Pound"],
            dataDimensionClass: ["Centimeter", "Inch"],
            showAttributeDialog: false,
            sizeGuide: false,
            sizeGuides: [],
            attribute: {
                title_en: "",
                title_ar: "",
                description_en: "",
                description_ar: "",
                image: null,
                isImage: false
            },
            subcategories: [],
            options: [],
            variantsHeaders: [
                { text: "Variant", value: "name" },
                { text: "Price", value: "price" },
                { text: "Stock", value: "quantity" },
                { text: "Visibility", value: "visibility" }
            ],
            defaultVariantId: "",
            defaultPreviewId: ""
        };
    },
Enter fullscreen mode Exit fullscreen mode
Collapse
sparshcodes profile image
Sparsh Gupta

Hi, just wanted to ask ,
since we are changing the state in handleInputChange
therefore, every time we will type in something the whole component will re-render since the state is changing
so isn't it an expensive approach in respect to performance?

Collapse
yahaya_hk profile image
Yahaya Kehinde

Amazing Deborah ! ☺️☺️

Collapse
knightwarrior93 profile image
SHAMIM

Thanks for providing with an useful information in a synchronize manner ...

Collapse
majidmo49787329 profile image
Majid Mohamed

Amazing perofrmance. Very thanks!

Collapse
salvadorbeltrandev profile image
Salvador Beltrán

Thank you so much, I was handling this terrible, but with your handleChange I was able to refactor the entire event! fav*

Collapse
abned08 profile image
Aboubakr Ned

Wow, that exactly what I was looking for

Collapse
juliojeanfils1 profile image
Julio Jean Fils

Thank you

Collapse
moreyogesh profile image
Yogesh More

The spread operator has not used the in-class component. and this what I am looking for thanks for the exact answer :)

Collapse
jokanic profile image
Jokanic

Really clean.

Collapse
haleemik profile image
haleemik

Can we map the input filed using an array of states. So that we can reduce the code size. Is it possible to do that?

Collapse
chhadi profile image
Ch_Hadi

Noice , i like that Deborah

Collapse
philjotham14 profile image
PhilJotham14

for example if I wanted to insert two value in input how do I go by that
value={values.position} for example onChange=" {(e)=> this.onChangeInput(e)}; { handleChange }; "

Collapse
gsingh profile image
G.Singh

Thank you so much!

Collapse
suhrobmuboraksho profile image
Suhrob Muborakshoev

Thanks for the explanation. Kudos

Collapse
omokay profile image
Omoke Chuku

This is beautiful!

Collapse
krishnacyber profile image
krishna-cyber

thanks a lot iam actually looking for it thanks again