0

I have a state that contains an array of objects. I added order so I can group the object based on the order. A new bunch of objects will be created when a button is clicked and the new objects order value will increase

const initialData = [
  {
    name: "point",
    label: "Point",
    order: 1
  },
  {
    name: "optionA",
    label: "A",
    order: 1
  },
  {
    name: "optionB",
    label: "B",
    order: 1
  },
];

Here is what I tried. I used reduce to group the data based on order when a button is clicked. The order value changes but the array the new array is not populating

const [formData, setFormData] = useState(initialData)

const addMoreField = () => {
    const groups = formData.reduce(
      (groups, item) => ({
        ...groups,
        [item.order]: [
          ...(groups[item.order] || []),
          { ...item, order: item.order++ }
        ]
      }),
      {}
    );

setFormData(groups)

 console.log("groups", groups);
    console.log("formData", formData);
}

The addMoreField function is supposed to increment the order and create a new array with more groups.

const newData = [
1: [
  {
    name: "point",
    label: "Point",
    order: 1
  },
  {
    name: "optionA",
    label: "A",
    order: 1
  },
  {
    name: "optionB",
    label: "B",
    order: 1
  },
],

2: [
  {
    name: "point",
    label: "Point",
    order: 2
  },
  {
    name: "optionA",
    label: "A",
    order: 2
  },
  {
    name: "optionB",
    label: "B",
    order: 2
  },
]
]

implementation on codesandbox

2
  • Change it to item.order+1, item.order++ increments order in the original state (which is not good), but returns the value of item.order before any increment occurs Commented Dec 21, 2022 at 23:21
  • I did but i am not getting new groups of data Commented Dec 21, 2022 at 23:52

1 Answer 1

2

Update

Not sure if I fully understand the goal, but I did some modification in the posted sandbox so that the solution in the original answer is wired up with existing code.

Also modified the onChange handler a bit so that it correctly record data to state object for the output with submit.

Forked demo with modifications: codesandbox

Firstly the use of initial value changed so that it can be properly mapped:

const [formData, setFormData] = React.useState([initialData]);

Also changed the format for map() as the following. The onChange is modified so that it accepts value from multiple fields to add to the state object:

<form>
  <div className="wrapper">
    {formData.map((field, index) => (
      <div key={index}>
        <p>{`field ${index + 1}`}</p>
        {field.map((data, index) => (
          <div key={index} className="form-data">
            <label>{data.name}</label>
            <input
              type="text"
              name={data.name}
              onChange={(e) =>
                setState((prev) => ({
                  ...prev,
                  [data.order]: {
                    ...prev[data.order],
                    [data.name]: e.target.value,
                  },
                }))
              }
            />
          </div>
        ))}
      </div>
    ))}
  </div>
</form>

Original

It seems that the posted initialData is an array but the newData looks like an object. According to description in the question, perhaps it should be an array of "group" but adding a new group with order + 1 in the desired result?

The below example modifies addMoreField so that it first find the latest order from old groups, and then return a new array adding the new group with latestOrder + 1.

Some lists of initial and new data are also added but they're for testing only.

This example runs in snippets for convenience:

const initialData = [
  {
    name: "point",
    label: "Point",
    order: 1,
  },
  {
    name: "optionA",
    label: "A",
    order: 1,
  },
  {
    name: "optionB",
    label: "B",
    order: 1,
  },
];

const App = () => {
  const [formData, setFormData] = React.useState(initialData);

  const addMoreField = () =>
    setFormData((prev) => {
      const oldGroups = Array.isArray(prev[0]) ? [...prev] : [[...prev]];
      const latestOrder = oldGroups.reduce(
        (acc, cur) => (cur[0].order > acc ? cur[0].order : acc),
        0
      );
      const newGroup = oldGroups[0].map((item) => ({
        ...item,
        order: latestOrder + 1,
      }));
      return [...oldGroups, newGroup];
    });

  return (
    <main>
      <button onClick={addMoreField}>ADD GROUP</button>
      <section>
        <div>
          <h3>👇 initialData: </h3>
          {initialData.map((item, index) => (
            <ul key={index}>
              <li>{`name: ${item.name}`}</li>
              <li>{`label: ${item.label}`}</li>
              <li>{`order: ${item.order}`}</li>
            </ul>
          ))}
        </div>
        {Array.isArray(formData[0]) && (
          <div>
            <h3>👇 Current formData (newData): </h3>
            {formData.map((group, index) => (
              <React.Fragment key={index}>
                {group.map((item, index) => (
                  <ul key={index}>
                    <li>{`name: ${item.name}`}</li>
                    <li>{`label: ${item.label}`}</li>
                    <li>{`order: ${item.order}`}</li>
                  </ul>
                ))}
              </React.Fragment>
            ))}
          </div>
        )}
      </section>
    </main>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root")).render(
  <App />
);
section {
  display: flex;
  gap: 80px;
}

button {
  padding: 9px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

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

4 Comments

Thank you. It gives me the expected output. However I used useEffect to load the Current formData when the page loads, but the data render multiple times. I would like the form to display instantly when the page loads and subsequently clicking on the button to add more filed
@Haq Indeed this only considered the posted scenario where initial data is given. Perhaps share some more details about the use case so that we can see if we might be possible to help?
I have added codesandbox link to the question. The use case is when a user want to submit batch data. In this scenario when a button is clicked a copy of that field should display and the data can be summited at once
@Haq Thanks for the update, not sure if I fully understand the goal but I wired up the sandbox code with the solution from the answer, so that it might be further adjusted to meet desired result. Details are updated in the answer as well and hopefully it could help.

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.