Haq
Haq

Reputation: 55

How to group array of objects and update the state in react React

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

Upvotes: 0

Views: 174

Answers (1)

John Li
John Li

Reputation: 7447

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>

Upvotes: 2

Related Questions