Reputation: 75
I am trying to push an obj into an array located in a nested object. What would be the best approach because I want it to render the component as well with the new obj.
What I want to achieve with this is for the user to be able to create new categories for the tasks
const [formElements, setFormElements] = useState({
task: {
type: 'text',
value: 'text',
className: 'task-input'
},
categories: {
type: 'select',
value: 'select',
className: 'categories-input',
cat: [
{value: 'money', displayValue: 'Money'},
{value: 'health', displayValue: 'Health'},
{value: 'daily', displayValue: 'Daily'},
{value: 'food', displayValue: 'Food'}
]
}
})
const buttonAddCategoryHandler = (e) => {
e.preventDefault()
setFormElements(prevState => {
???
})
}
I know this might be a bad question but I have a problem understanding how to update the state in a situation like this. Or this way of having a state is bad from the start?
Upvotes: 2
Views: 664
Reputation: 66
There are several ways to achieve this, but try using spread operator:
setFormElements((prevState) => {
return({
...prevState,
categories: {
...prevState.categories,
cat: [
[...prevState.categories.cat,
{value: '123', displayValue: 'abc'}]
]
}
})
})
Upvotes: 1
Reputation: 2381
I would take a look at using the slightly more advanced hook; useReducer
. Check out this note from the react docs:
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax. Another option is useReducer, which is more suited for managing state objects that contain multiple sub-values.
Using useReducer
, your example above might look something like:
const [state, dispatch] = useReducer((state, action) => {
switch(action.type){
case "UPDATE_CAT":
return {
...state,
categories: {
...state.categories,
cat: [...state.cat, action.value]
}
}
default:
return state
}
})
const buttonAddCategoryHandler = (e) => {
e.preventDefault()
setFormElements(prevState => {
dispatch({ type: "UPDATE_CAT", value: { value: "vitamins", displayValue: "Vitamins" } })
})
}
Upvotes: 1
Reputation: 2655
const [formElements, setFormElements] = useState({
task: {
type: 'text',
value: 'text',
className: 'task-input'
},
categories: {
type: 'select',
value: 'select',
className: 'categories-input',
cat: [
{value: 'money', displayValue: 'Money'},
{value: 'health', displayValue: 'Health'},
{value: 'daily', displayValue: 'Daily'},
{value: 'food', displayValue: 'Food'}
]
}
});
const updatedCategories = (categories) => {
const catUpdated = categories.cat;
catUpdated.concat({value: 'new', displayValue: 'NEW'});
return {
...categories,
cat: catUpdated
}
}
const buttonAddCategoryHandler = (e) => {
e.preventDefault()
const updatedState = {
...formElements,
categories: updatedCategories(formElements.categories)
}
setFormElements(updatedState);
}
You can use your old state and update it before making another setState.
Upvotes: 0
Reputation: 1484
const buttonAddCategoryHandler = (e) => {
e.preventDefault()
const newArray = [...formElements.categories.cat, {value: 'newVal', displayValue: 'Friendly Value'}]
setFormElements({
...formElements,
categories: {
...formElements.categories,
cat: newArray
}
})
}
Upvotes: 2