How to store and update arrays in React useState hook

How to store and update arrays in React useState hook

·

9 min read

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:

npx create-react-app react-usestate-array

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

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:

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:

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 method and spread operators to return the updated array.

An alternative way is to get the updated array using map function:

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:

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

As you can see in the commented code, you can use concat method as well.

Don't forget to bind addItemToStart function to the onClick handler!

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:

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:

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:

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).

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):

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:

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:

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:

[
  { "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:

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:

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:

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