Nima Postanchi
Nima Postanchi

Reputation: 35

how to set a nested object in React Hooks

I'm working on a form which contains a section where users can choose the quantity and category of a concert ticket in a dropdown. This component is dynamically created and it can have more items(quantity + category). The dropdowns of the categories are related to each other. So if you choose one category the value of the other dropdowns should be set to the same selected value. But I also need the name of each category dropdown for validation.

This is how I created the category object:

const createCategory = () => {
        let cat = {}
        let nestedCat = {}
        for (let it in item) {
            for (let bt in item[it].buyertypes) {
                cat2[item[it].buyertypes[bt].id] = ''
            }
            cat[item[it].id] = nestedCat
            nestedCat = {}
        }
        return cat
    } 
    const [category, setCategory] = useState(createCategory());

This is the initial object:

{​
  9871: { 1: "", 2: "", 3: "" }
​
  9872: { 5: "", 6: "", 8: "" }
}

How I show and handle the ticket component

  const handleQuantity = e => {
      setQuantity({
          ...quantity,
          [e.target.name]: e.target.value
      });
  }

  const handleCategory = e => {
      setCategory({
          ...category,
          [e.target.name]: e.target.value
      });
 }

 const ShowData = () => {
        let ticketIem = []
        for (let it in item) {
            for (let bt in item[it].buyertypes) {
                let buyerType = item[it].buyertypes[bt]
                ticketIem.push({
                    'price': buyerType.prices, 'catId': item[it].id,
                    'qntId': item[it].buyertypes[bt].id
                })
            }
        }

        return (
            <div>
                {ticketIem.map((i, index) => (
                    <div key={index} >
                        <div>
                            <label>Number</label>
                            <select
                                value={quantity[i.qntId]}
                                onChange={handleQuantity}
                                name={i.qntId}
                            >
                                <option value={0}>0</option>
                                <option value={1}>1</option>
                            </select>
                        </div>
                        <div>
                            <label>Category</label>
                            <select
                                value={category[i.catId]}
                                onChange={handleCategory}
                                name={i.catId}
                            >
                                <option value="">Choose</option>
                                {categories.map((cat, index) => (
                                    <option key={index} value={cat.id} name={i.qntId}>
                                        {cat.description} {cat.value}
                                    </option>
                                ))}
                            </select>
                        </div>
                    </div>
                ))}
            </div>
        )
    }

After selecting an option in the dropdown the category object will be set like:

{
  9871: "11", 9872: "7"
}

but I expect:

{​
  9871: { 1: "11", 2: "11", 3: "11" }
​
  9872: { 5: "7", 6: "7", 8: "7" }
}

Upvotes: 1

Views: 1482

Answers (1)

Drew Reese
Drew Reese

Reputation: 203427

If you want to set all the nested properties of a specific category or quantity then you'll need to also iterate the keys of those nested properties. Use Object.keys to get an array of the nested object's keys, and reduce them into a new object with the new value set for each key.

I suggest also using a functional state update since each update depends on the existing/current state as it is shallowly copied into the next state.

const handleQuantity = (e) => {
  setQuantity((quantity) => ({
    ...quantity,
    [e.target.name]: Object.keys(quantity[e.target.name]).reduce(
      (obj, key) => ({
        ...obj,
        [key]: e.target.value
      }),
      {}
    )
  }));
};

const handleCategory = (e) => {
  setCategory((category) => ({
    ...category,
    [e.target.name]: Object.keys(category[e.target.name]).reduce(
      (obj, key) => ({
        ...obj,
        [key]: e.target.value
      }),
      {}
    )
  }));
};

Upvotes: 1

Related Questions