How to store and update arrays in React useState hook
Tags: react
Published: 2022-07-18 10:17:00

You might have come across different use cases where you would want to store an array in the React state and later modify them. In this article, we will see different ways of doing it.

## Project setup

Create a react project by running the following command:

```bash
npx create-react-app react-usestate-array
```

Update the `index.css` file with the following code for styling the app:

```css
body {
  display: flex;
  justify-content: center;
}

.App {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

ul {
  padding: 0;
}

button {
  margin: 0.5rem;
  cursor: pointer;
}

ul li {
  display: flex;
  align-items: center;
  list-style-type: disc;
  justify-content: space-between;
}
```

Update `App.js` with the following code:

```jsx
import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)

  return (
    <div className="App">
      <div>
        <button>Add Item to Start</button>
        <button>Add Item to End</button>
        <button>Add Item in between</button>
      </div>
      <div>
        <button>Delete Item from Start</button>
        <button>Delete Item from End</button>
        <button onClick>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment">+</button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
```

Here we are creating a list of random numbers, initializing a local state with the list of random numbers, and displaying them. Against each number in the list, we have a button to increment it. Also, we have buttons to modify the list.

## Modifying an item in the array

First, let's make the increment buttons working:

```jsx
import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)
  const incrementNumber = index => {
    setList(existingItems => {
      return [
        ...existingItems.slice(0, index),
        existingItems[index] + 1,
        ...existingItems.slice(index + 1),
      ]
    })
  }
  return (
    <div className="App">
      <div>
        <button>Add Item to Start</button>
        <button>Add Item to End</button>
        <button>Add Item in between</button>
      </div>
      <div>
        <button>Delete Item from Start</button>
        <button>Delete Item from End</button>
        <button onClick>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment" onClick={() => incrementNumber(i)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
```

As you may be aware, we should not directly modify the state.
Hence we use the callback, which is the second argument to `setList` function. The callback receives an argument, which is the existing state and we make use of the [slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)
method and [spread operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) to return the updated array.

An alternative way is to get the updated array using [map function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map):

```js
const incrementNumber = index => {
  setList(existingItems => {
    return existingItems.map((item, j) => {
      return j === index ? item + 1 : item
    })
  })
}
```

Here inside the map function, we check if the passed index is the same as the current index, then we increment the number by one otherwise we return the same number.

## Adding Items to the array

We will see how to add an item to the beginning, end, and somewhere in between the array.

### Adding item to the start of the array:

We can add item by using spread operator as shown below:

```js
const addItemToStart = () => {
  setList(existingItems => {
    return [getRandomNumber(), ...existingItems]
    // return [getRandomNumber()].concat(existingItems);
  })
}
```

As you can see in the commented code, you can use [concat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) method as well.

Don't forget to bind `addItemToStart` function to the [onClick handler](https://www.codingdeft.com/posts/react-onclick-event-with-examples/#updating-a-state-on-click-of-a-button)!

```jsx
import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)
  const incrementNumber = index => {
    setList(existingItems => {
      return [
        ...existingItems.slice(0, index),
        existingItems[index] + 1,
        ...existingItems.slice(index + 1),
      ]
      // return existingItems.map((item, j) => {
      //   return j === index ? item + 1 : item;
      // });
    })
  }

  const addItemToStart = () => {
    setList(existingItems => {
      return [getRandomNumber(), ...existingItems]
      // return [getRandomNumber()].concat(existingItems);
    })
  }

  return (
    <div className="App">
      <div>
        <button onClick={addItemToStart}>Add Item to Start</button>
        <button>Add Item to End</button>
        <button>Add Item in between</button>
      </div>
      <div>
        <button>Delete Item from Start</button>
        <button>Delete Item from End</button>
        <button onClick>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment" onClick={() => incrementNumber(i)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
```

### Adding item to the end of the array

Similar to adding item to the start of the array, we can make use of spread operator by modifying the order:

```js
const addItemToEnd = () => {
  setList(existingItems => {
    return [...existingItems, getRandomNumber()]
    // return existingItems.concat([getRandomNumber()]);
  })
}
```

An alternative approach with the concat method can also be used as shown in the commented code above.

### Adding Item in between of the array

Say if you have to add an item in a particular index and then shift the rest of the items to the right by 1 index, you can do that by using slice and spread operator as shown below:

```js
const addItemInBetween = () => {
  setList(existingItems => {
    const randomIndex = getRandomNumber(existingItems.length)
    const randomNumber = getRandomNumber()
    return [
      ...existingItems.slice(0, randomIndex),
      randomNumber,
      ...existingItems.slice(randomIndex),
    ]
  })
}
```

Here we have randomly generated an index, you can hard code it to some value and see if it is updating correctly.

We can use reduce method as well as shown below for adding an item in between:

```js
const addItemInBetween = () => {
  setList(existingItems => {
    const randomIndex = getRandomNumber(existingItems.length)
    const randomNumber = getRandomNumber()

    return existingItems.reduce(
      (prev, x, i) => prev.concat(i === randomIndex ? [randomNumber, x] : x),
      []
    )
  })
}
```

Here inside the reduce method callback, if the index is the same as that of the index to be updated, then we concatenate the previous array with an array of the number to be inserted and the current item. Otherwise, we just concatenate the current item to the previous array.

## Deleting items from the array

While deleting as well, we will see how to delete from the start, end, and in between the array.

### Deleting an item from the start of the array

Here as well we can use the slice method. When we pass 1 as the first argument to the slice method, it returns all the items starting from the first index (all items except the first one, since the array index starts from 0).

```jsx
const deleteItemFromStart = () => {
  setList(existingItems => {
    return existingItems.slice(1)
    // return existingItems.filter((item, i) => i !== 0);
  })
}
```

As you can see, we can use the `filter` method as well, where we check if the index is 0 and if so then we filter it out.

### Deleting an item from the end of the array

The last index of the array can be found using `Array.length - 1` so in order to remove the last item, we can do `Array.slice(0, Array.length - 1)`:

```jsx
const deleteItemFromEnd = () => {
  setList(existingItems => {
    return existingItems.slice(0, existingItems.length - 1)
    // return existingItems.filter((item, i) => i !== existingItems.length - 1);
  })
}
```

Even the `filter` function can also be used as shown in the commented code.

### Deleting an item in between of the array

While deleting from a particular position, we can use the combination of slice method and spread operator:

```js
const removeItemInBetween = () => {
  setList(existingItems => {
    const randomIndex = getRandomNumber(existingItems.length)
    return [
      ...existingItems.slice(0, randomIndex),
      ...existingItems.slice(randomIndex + 1),
    ]

    // return existingItems.reduce(
    //   (prev, x, i) => prev.concat(i === randomIndex ? [] : x),
    //   []
    // );
  })
}
```

As you can see, we have spread items before the index and after the index and added them to a brand new array. This can also be achieved using reduce method, similar to adding an item at a specified index, except that we are concatenating an empty array when the index is matched, thus skipping it.

## Final code

Here is the final code with all the operations together:

```jsx
import { useState } from "react"

function getRandomNumber(max = 100) {
  return Math.floor(Math.random() * max)
}
const INITIAL_LIST = Array.from({ length: 5 }, () => getRandomNumber())

function App() {
  const [list, setList] = useState(INITIAL_LIST)
  const incrementNumber = index => {
    setList(existingItems => {
      return [
        ...existingItems.slice(0, index),
        existingItems[index] + 1,
        ...existingItems.slice(index + 1),
      ]
      // return existingItems.map((item, j) => {
      //   return j === index ? item + 1 : item;
      // });
    })
  }

  const addItemToStart = () => {
    setList(existingItems => {
      return [getRandomNumber(), ...existingItems]
      // return [getRandomNumber()].concat(existingItems);
    })
  }

  const addItemToEnd = () => {
    setList(existingItems => {
      return [...existingItems, getRandomNumber()]
      // return existingItems.concat([getRandomNumber()]);
    })
  }

  const deleteItemFromStart = () => {
    setList(existingItems => {
      return existingItems.slice(1)
      // return existingItems.filter((item, i) => i !== 0);
    })
  }

  const deleteItemFromEnd = () => {
    setList(existingItems => {
      return existingItems.slice(0, existingItems.length - 1)
      // return existingItems.filter((item, i) => i !== existingItems.length - 1);
    })
  }

  const addItemInBetween = () => {
    setList(existingItems => {
      const randomIndex = getRandomNumber(existingItems.length)
      const randomNumber = getRandomNumber()
      return [
        ...existingItems.slice(0, randomIndex),
        randomNumber,
        ...existingItems.slice(randomIndex),
      ]

      // return existingItems.reduce(
      //   (prev, x, i) => prev.concat(i === randomIndex ? [randomNumber, x] : x),
      //   []
      // );
    })
  }

  const removeItemInBetween = () => {
    setList(existingItems => {
      const randomIndex = getRandomNumber(existingItems.length)
      return [
        ...existingItems.slice(0, randomIndex),
        ...existingItems.slice(randomIndex + 1),
      ]

      // return existingItems.reduce(
      //   (prev, x, i) => prev.concat(i === randomIndex ? [] : x),
      //   []
      // );
    })
  }
  return (
    <div className="App">
      <div>
        <button onClick={addItemToStart}>Add Item to Start</button>
        <button onClick={addItemToEnd}>Add Item to End</button>
        <button onClick={addItemInBetween}>Add Item in between</button>
      </div>
      <div>
        <button onClick={deleteItemFromStart}>Delete Item from Start</button>
        <button onClick={deleteItemFromEnd}>Delete Item from End</button>
        <button onClick={removeItemInBetween}>Delete Item from between</button>
      </div>
      <ul>
        {list.map((item, i) => {
          return (
            <li key={i}>
              <span>{item}</span>
              <button title="increment" onClick={() => incrementNumber(i)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default App
```

## Updating an array of objects

Consider the below array:

```json
[
  { "id": 1001, "score": 250 },
  { "id": 1002, "score": 100 },
  { "id": 1003, "score": 300 }
]
```

If you have an array of objects with unique ids assigned to each object and you want to modify the array based on the id, then you can achieve it by the following function:

```js
const incrementScore = currentId => {
  setScore(existingItems => {
    const itemIndex = existingItems.findIndex(item => item.id === currentId)
    return [
      ...existingItems.slice(0, itemIndex),
      {
        // spread all the other items in the object and update only the score
        ...existingItems[itemIndex],
        score: existingItems[itemIndex].score + 1,
      },
      ...existingItems.slice(itemIndex + 1),
    ]
  })
}
```

Same functionality can be achieved using map function as shown below:

```js
const incrementScore = currentId => {
  setScore(existingItems => {
    return existingItems.map(item => {
      return item.id === currentId ? { ...item, score: item.score + 1 } : item
    })
  })
}
```

Here is the complete code using the above functions:

```jsx
import { useState } from "react"

const INITIAL_SCORES = [
  { id: 1001, score: 250 },
  { id: 1002, score: 100 },
  { id: 1003, score: 300 },
]

function Scores() {
  const [score, setScore] = useState(INITIAL_SCORES)

  const incrementScore = currentId => {
    setScore(existingItems => {
      const itemIndex = existingItems.findIndex(item => item.id === currentId)
      return [
        ...existingItems.slice(0, itemIndex),
        {
          // spread all the other items in the object and update only the score
          ...existingItems[itemIndex],
          score: existingItems[itemIndex].score + 1,
        },
        ...existingItems.slice(itemIndex + 1),
      ]
      //   return existingItems.map((item) => {
      //     return item.id === currentId
      //       ? { ...item, score: item.score + 1 }
      //       : item;
      //   });
    })
  }

  return (
    <div className="App">
      <ul>
        {score.map(item => {
          return (
            <li key={item.id}>
              <span>{item.score}</span>
              <button title="increment" onClick={() => incrementScore(item.id)}>
                +
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default Scores
```