0

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: "",
});
  };
2
  • 1
    please may you edit the question and include a minimal reproducible example? links to off-site code must be accompanied by code in the question itself Commented Mar 24, 2021 at 13:46
  • 1
    Thank you for the link provided ,it's true that I didn't make the whole post like it should be. Anyway I was able to make it work...stupid me! Commented Mar 24, 2021 at 14:51

1 Answer 1

2

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)

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for taking the time and provide such a great explanation, the solution you provided using Object.assign is what I currently need in order to make my code cleaner :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.