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 (22)

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
 
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
 
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
 
jocanola profile image
Jocanola

Really clean.

Collapse
 
gsingh profile image
G.Singh

Thank you so much!

Collapse
 
omokay profile image
Omoke Chuku

This is beautiful!

Collapse
 
girinuri123 profile image
Giri

Awesome article!