Reputation: 153
Hi,
I'm creating a form to create virtual machine. Informations are gathered across multiple pages (components) and centralized in top level component.
It's state object looks like this :
const [vmProp, setVmProp] = useState({
name: "",
image_id: "",
image_name: "",
volumesList: [],
RAM: "",
vcpu: "",
autostart: true,
});
I want to be able to add/remove a volume to the volumes List and I want volumesList to looks like this :
[
{
name: "main",
size: 1024
},
{
name: "backup",
size: 2048
},
{
name: "export",
size: 2048
}
]
For now I've only tried to add a volume.
const handleAddVolume = (obj) => {
setVmProp({ ...vmProp,
volumesList: {
...vmProp.volumesList,
[obj.name]: {
size: obj.size,
},
} });
};
It's working but the output is :
[
name: {
size: 1024
}
]
const handleAddVolume = (obj) => {
setVmProp({ ...vmProp,
volumesList: {
...vmProp.volumesList,
{
name: obj.name,
size: obj.size,
},
} });
};
output :
[
name: "main",
size: 1024
]
Do you have any ideas on how to handle such state object ? Thanks
Upvotes: 0
Views: 103
Reputation: 153
As I needed to be able to add / remove / replace an object in my array I decided to create it's own state as following :
const [volumesList, setVolumesList] = useState([]);
const handleAddVolume = (obj) => {
setVolumesList((oldList) => [...oldList, obj]);
};
const handleRemoveVolume = (obj) => {
setVolumesList((oldList) => oldList.filter((item) => item.name !== obj.name));
};
const handleEditVolume = (obj) => {
setVolumesList(
volumesList.map((volume) =>
(volume.name === obj.name
? { ...volume, ...obj }
: volume),
),
);
};
Thanks for all your answers !
Upvotes: 0
Reputation: 2023
Here is a working example:
const Example = (props) => {
const inputRef = createRef();
const [vmProp, setVmProp] = useState({
name: "",
image_id: "",
image_name: "",
volumesList: [],
RAM: "",
vcpu: "",
autostart: true,
});
const { volumesList } = vmProp;
const handleAddVolume = (e) => {
e.preventDefault();
const input = inputRef.current.value;
setVmProp((prevVmProp) => {
const newVmProp = { ...prevVmProp };
newVmProp.volumesList.push({
name: input,
size: 1024,
});
return newVmProp;
});
// reset the input
inputRef.current.value = "";
};
return (
<>
<form>
<input ref={inputRef} type="text" />
<button onClick={handleAddVolume}>Add volume</button>
</form>
{volumesList.map((volume) => (
<div key={volume.name}>{`${volume.name} - ${volume.size}`}</div>
))}
</>
);
};
export default Example;
it's just a proof of concept. Basically, setVmProp
accepts a function that gets the up-to-date state value as the update is asynchornus and returns the new value of the state. so using ES6's destructuring function, I make a copy of the latest value of vmProp
called newVmProp
, then push a new object to newVmProp.volumesList
then return newVmProp
that contains the newly added volume + everything else. The value of the name
I get from an input field. Again this is just a proof of concept.
Upvotes: 0
Reputation: 6736
volumesList shouldn't be object, make it as an array inside handleAddVolume function.
Try the below approach,
const handleAddVolume = (obj) => {
setVmProp({ ...vmProp,
volumesList: [
...vmProp.volumesList,
{
name: obj.name,
size: obj.size,
}
] });
};
Upvotes: 1
Reputation: 168824
You'll have a better time if you don't need to nest/unnest the volumes list from the other state. (Even more idiomatic would be to have a state atom for each of these values, but for simple values such as booleans and strings, this will do.)
const [vmProps, setVmProps] = useState({
name: "",
image_id: "",
image_name: "",
RAM: "",
vcpu: "",
autostart: true,
});
const [volumesList, setVolumesList] = useState([]);
Upvotes: 1