Smoogy
Smoogy

Reputation: 45

Issue adding value to an array of object via dynamic input field

I've been following a YouTube tutorial on how to add or remove input fields dynamically with React.

Most of the tutorial was easy to follow, I almost achieved what I wanted to do but I have a problem when I add an object, it duplicates itself instead of adding the different values.

Here is the code:

    import React, { useState } from "react";
import { Button, Form } from "react-bootstrap";

const countries = [
  {
    key: "1",
    name: "",
    value: ""
  },
  {
    key: "2",
    name: "Afghanistan",
    value: "Afghanistan"
  },
  {
    key: "3",
    name: "Albania",
    value: "Albania"
  },
  {
    key: "4",
    name: "Algeria",
    value: "Algeria"
  },
  {
    key: "5",
    name: "Angola",
    value: "Angola"
  },
  {
    key: "6",
    name: "Argentina",
    value: "Argentina"
  },
]

const listanswers = [
  {
    key: '01',
    name: '',
    value: '',
  },
  {
    key: '02',
    name: 'Yes',
    value: 'Yes',
  },
  {
    key: '03',
    name: 'No',
    value: 'No',
  },
  {
    key: '04',
    name: 'Unsure',
    value: 'Unsure',
  },
];


export default function Section1_3() {
  const [question1_7, setQuestion1_7] = useState("");
  const [instance1_7, setInstance1_7] = useState([
    {
      Country: "",
      Answer: "",
    }
  ]);

  // handle input change
  const handleInputChange = (instance, setinstance, e, index) => {
    const { name, value } = e.target;
    const list = [...instance];
    list[index][name] = value;
    setinstance(list);
  };

  // handle click event of the Remove button
  const handlRemoveInstance = (instance, setinstance, index) => {
    const list = [...instance];
    list.splice(index, 1);
    setinstance(list);
  };

  // handle click event of the Add button
  const handleAddInstance = (instance, setinstance) => {
    setinstance([...instance, instance[0]]);
  };

  return (
    <div>
      <Form>
        <Form.Group size="lg" controlId="formSection3">
          {instance1_7.map((answer, i) => {
            return (
              <div key={(i + 1) * 3}>
                <Form.Label>Instance variable 1</Form.Label>
                {console.log(answer)}
                <Form.Control
                  name="Country"
                  as="select"
                  onChange={e =>
                    handleInputChange(instance1_7, setInstance1_7, e, i)
                  }
                >
                  {countries.map(country => (
                    <option
                      key={country.key}
                      value={country.value}
                      label={country.name}
                    />
                  ))}
                </Form.Control>
                <Form.Label>Instance variable 2</Form.Label>
                <Form.Control
                  name="Answer"
                  as="select"
                  onChange={e =>
                    handleInputChange(instance1_7, setInstance1_7, e, i)
                  }
                >
                  {listanswers.map(answer => (
                    <>
                      <option
                        key={answer.key}
                        value={answer.value}
                        label={answer.name}
                      />
                    </>
                  ))}
                </Form.Control>

                {instance1_7.length !== 1 && (
                  <Button
                    variant="danger"
                    onClick={() =>
                      handlRemoveInstance(instance1_7, setInstance1_7, i)
                    }
                  >
                    Remove
                  </Button>
                )}
                {instance1_7.length - 1 === i && (
                  <Button
                    variant="success"
                    onClick={() =>
                      handleAddInstance(instance1_7, setInstance1_7, i)
                    }
                  >
                    Add
                  </Button>
                )}
              </div>
            );
          })}
        </Form.Group>
      </Form>
      <div style={{ marginTop: 20 }}>{JSON.stringify(instance1_7)}</div>
    </div>
  );
}

I don't know how to explain it properly, so I created a StackBlitz here : https://stackblitz.com/edit/react-dm6jwd?file=src%2FApp.js

Also if you have any suggestion on how to implement easily with a third party package that could be nice.

Thanks!

Edit:

Found solution, I added the array of the object instead of putting the object directly in the HandleAddInput function

  // handle click event of the Add button
  const handleAddInstance = (instance, setinstance) => {
    setinstance([...instance,  {
  Country: "",
  Answer: "",
});
  };

Upvotes: 0

Views: 129

Answers (1)

Damien Wilson
Damien Wilson

Reputation: 354

Update: I just realised you'd posted your solution. :o) What follows is an explanation of what the problem was.

Would you take a look at your code on line 86?

// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
   setinstance([...instance, instance[0]]);
};

You'll see that there is a copying action on instance[0]. In javascript, when you pass an object as a copy, the object is passed as a reference. Once you change the values on the newly added object, it will update values on other references too.

If you intend to copy you will need to create a clone.

There are multiple ways of doing this. For instance, you could use the JSON API to remove referencing:

JSON.parse(JSON.stringify(instance[0]))

This is slower than:

Object.assign({}, instance[0])

Benchmark: https://www.measurethat.net/Benchmarks/Show/2471/2/objectassign-vs-json-stringparse#latest_results_block

A closer look at Object.assign(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


Or, maybe you could require a function provided by lodash called cloneDeep and import in the following way:

import cloneDeep from 'lodash/cloneDeep';

And then:

setinstance([...instance, cloneDeep(instance[0]]));

In closing; I would suggest using the native assign method Object.assign({}, instance[0]).

// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
   setinstance([...instance, Object.assign({}, instance[0])]);
};

Best of luck :o)

Upvotes: 2

Related Questions