Reputation: 55
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
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