2

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?

4 Answers 4

2
   const buttonAddCategoryHandler = (e) => {
        e.preventDefault()
        const newArray = [...formElements.categories.cat, {value: 'newVal', displayValue: 'Friendly Value'}]
        setFormElements({
          ...formElements,
          categories: {
             ...formElements.categories,
             cat: newArray
             
          }

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

Comments

1

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" } })
        })
    }

Comments

1

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'}]
      ]
    }
  })
})  

Comments

0

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.

Comments

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.