简体   繁体   中英

How to update an array by index using the useState hook?

I have a select component that I would like to use to update the values of an array of objects based on the index.

I use the hook like this:

const [areas, setAreas] = useState(product.areas);

This returns the following for "areas":

[
  0: {de: "Getraenke", en: "Drinks"},
  1: {de: "Snacks", en: "Snacks"}
]

My select component looks like this:

{areas?.map((area: {de: string, en: string}, index: number) => (
  <Wrapper key={index}>
    <Select
      label="Area"
      name="product-area"
      value={area[lang] || ''}
      onChange={e => setArea([...area, { [lang]: e.target.value }])}
    >
      {areaOptions.map(option => (
        <option value={option} key={option}>
          {option}
        </option>
      ))}
    </Select>
  </InputWrapper>
))}

In this way I unfortunately get the following for "area" after selecting an option (here "Sweets"):

[
  0: {de: "Getraenke", en: "Drinks"},
  1: {de: "Snacks", en: "Snacks"}
  2: {en: "Sweets" }
]

So the first object of the array is not updated as intended, but a further object is appended, which unfortunately also lacks the "de" language.

My goal is to update the first object (or the object based on the index of the select component) of the array so that instead of:

  0: {de: "Getraenke", en: "Drinks"}

..the updated object looks like this:

  0: {de: "Suessigkeiten", en: "Sweets"}

The objects within the array should therefore be updated based on the index of the current select component and also take into account the selected language (default "en").

Let's solve this step by step

1. A further object is appended

That's because you're telling it to do so:)

onChange={e => setArea([...area, { [lang]: e.target.value }])}

This means, copy the entire array ...area and add a new object at the end { [lang]: e.target.value }

Another mistake you're making is not using a callback function, proper way to access current ...area should be this:

onChange={e => setArea((currentArea) => [...currentArea, { [lang]: e.target.value }]}

This way you always have the most up-to-date version of area .

Now, to update the specific object by the index, you would do the following:

onChange={e => setArea((currentArea) => {
  const { value } = e.target;
  
  const newArea = currentArea.map((singleArea, areaIndex) => {
    if (areaIndex === index) {
      return { [lang]: value }
    }
 
    return singleArea;
  });

  return newArea;
}}

2. Lacks the "de" language

Again, you're explicitly only adding one language to it:)

{ [lang]: value } // { en: 'something' }

Not sure how to fix this one for you, but at least you can understand why its wrong, It should be something like (just a concept, this won't work):

{ [langDe]: value, [langEn]: value } // { de: 'Guten Tag', en: 'something' }

The easiest way to do this is to clone the array, update the specific array item by index and then replace the old array with it using useState, like this.

const updateArea = (e, lang, index) => {
  const updatedAreas = [...areas];
  updatedArea[index][lang] = e.target.value;
  setAreas(updatedAreas);
}

...

{areas?.map((area: {de: string, en: string}, index: number) => (
  <Wrapper key={index}>
    <Select
      label="Area"
      name="product-area"
      value={area[lang] || ''}
      onChange={e => updateArea(e, lang, index)}
    >
      {areaOptions.map(option => (
        <option value={option} key={option}>
          {option}
        </option>
      ))}
    </Select>
  </InputWrapper>
))}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM