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! 🎊

Top comments (23)

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
 
jgifford82 profile image
jgifford82 •

Thank you so much!! I'm learning about controlled forms, and this has been incredibly helpful!

Collapse
 
donchocska profile image
donchocska •

Amazing perofrmance. Very thanks!

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
 
kkasaija profile image
Kasaija Kenneth • • Edited

This was so insightfull. But i have one quetion. how then would you reset the input fields upon form submission?. is it as simple as calling e.g setFormFields ( initialState )
Here my assumption is that initialState is an object e.g
const initialState={
someInput: ' ',
anotherInput: ' ',
}

Collapse
 
tombohub profile image
tombohub • • Edited

very good , but I don't like it. Now you have to maintain the names of the inputs, which is a string. It's a bug waiting to happen.

I suggest using this solution: stackoverflow.com/a/47707719/1079002

btw, do you have solution for nested objects? I need that, thanks!

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
 
yahaya_hk profile image
Yahaya Kehinde •

Amazing Deborah ! ☺️☺️

Collapse
 
svadr 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
 
knightwarrior93 profile image
SHAMIM •

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

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?