How to update a state array in Reactjs

Last updated : Jul 30, 2023 12:00 AM

When updating a state array, the safest approach is to create a copy of the array, do the modifications in that copy, and assign the modified copy back to the state. You should never mutate the state array directly.

This article explains the proper way to update an array stored as a state variable. We also discuss how not to update a state array.

Now, let's set up some foundation to illustrate our purpose. Take a look at the person interface listed below. The interface contains an array to hold email addresses.

interface person {
    name: string
    age: string
    contact: {email: string}[]
}

Let's initialize the above interface and store it in the state as the initial state.

let initialState: person = {
    name: "LearnBestCoding",
    age: "36",
    contact: [{email: "abc@learnbestcoding.com"}, {email: "aab@learnbestcoding.com"}]
}
Figure 1 : Updating a state array in Reactjs
Figure 1 : Updating a state array in Reactjs

The initialState is stored as a state variable.
const [person, setPerson] = useState(initialState)
Our purpose is to modify the contact array contained in the person object.

How to add a new element into a state array

There are a few steps to adding a new element into a state array.

  • Clone the array to be modified into a new variable
  • Modify the new array variable
  • Set the new array variable to the state

Now let's take a look at how to add a new email address to the contact array.

Example 1

In this method, we follow four steps for clarity. For a short version of this, check example 2.

  • Obtain the previous person object passed to the setPerson hook
  • Copy contact object from the previous person object
  • Add a new element to contact
  • Create a new person object with a new contact array merged
  • Assign the new person object to the state variable
const addEmail = () => {
   setPerson((prevPerson) => {
      let contact = [...prevPerson.contact]
      contact = [...contact, {email: Math.random()+"@learnbestcoding.com"}]
      let newPerson = {...prevPerson, contact:contact}
      return newPerson
   })
}

Example 2

This is the shorten form of Example 1.

const addEmail = () => {
   setPerson((prevPerson) => {
      return {...prevPerson, contact: [...prevPerson.contact, {email: Math.random()+"@learnbestcoding.com"}]}
   })
}

Example 3

This is how to add an element using array push method. Note that we clone the contact array and modify the clone to add a new email address.

const addEmail = () => {
  setPerson((person) => {
     const newContact = [...person.contact]
     newContact.push({email: Math.random()+"@learnbestcoding.com"})
     return {...person, contact: newContact}
  })
}

How not to update a state array

The React state should not be mutated directly. The below code produces unexpected results and has multiple issues.

  • We mutate the state directly by person.contact.push()
  • State update happens twice, resulting in two elements added at a time
//This doesn't work
const addEmail = () => {
  setPerson((person) => {
      person.contact.push({email: Math.random()+"@learnbestcoding.com"})
      return {...person}
  })
}

Here we mutate the state directly and return the same state variable. In that case React does not re-render the function to reflect the results.

//This doesn't work
const addEmail = () => {
    person.contact.push({email: Math.random()+"@learnbestcoding.com"})
    setPerson(person)
}

Here is the demo code to test all the scenarios I explained above. Uncomment a method at a time and see how it works.

import { useState } from 'react';

export const App = () => {
  interface person {
    name: string
    age: string
    contact: {email: string}[]
  }
  let initialState: person = {
    name: "LearnBestCoding",
    age: "36",
    contact: [{email: "abc@learnbestcoding.com"}, {email: "aab@learnbestcoding.com"}]
  }
    const inputStyle = {border: "1px solid black", height: 75, "padding": 10, "text-align": "center"}
    const displayStyle = {border: "1px solid black", height: 150, "padding": 10}
    const [person, setPerson] = useState<person>(initialState)

   const addEmail = () => {
/*
      //Example 1
      setPerson((prevPerson) => {
         let contact = [...prevPerson.contact]
         contact = [...contact, {email: Math.random()+"@learnbestcoding.com"}]
         let newPerson = {...prevPerson, contact:contact}
         return newPerson
      })
*/
/*
      //Example 2
      setPerson((prevPerson) => {
         return {...prevPerson, contact: [...prevPerson.contact, {email: Math.random()+"@learnbestcoding.com"}]}
      })
*/
/*
      //Example 3
      setPerson((person) => {
         const newContact = [...person.contact]
         newContact.push({email: Math.random()+"@learnbestcoding.com"})
         return {...person, contact: newContact}
      })
*/
/*
      //This doesn't work
      setPerson((person) => {
         person.contact.push({email: Math.random()+"@learnbestcoding.com"})
         return {...person}
      })
*/
/*
      //This doesn't work
      const addEmail = () => {
         person.contact.push({email: Math.random()+"@learnbestcoding.com"})
         setPerson(person)
      }
*/
   }
   return(
   <>
      <table>
         <tr>
            <td>
               <table style={inputStyle}>
                  <tbody>
                     <tr><td>Name:</td><td><input type="text" name="name" value={person.name}/></td></tr>
                     <tr><td>Age:</td><td><input type="number" name="age" value={person.age}/></td></tr>
                     {person.contact.map((email, index) => {
                        return <tr key={index}><td>Email:</td><td><input type="text" name="email" value={email.email} disabled/></td></tr>
                     })}
                     <tr><td colSpan={2}><button onClick={addEmail}>Add email</button></td></tr>
                  </tbody>
               </table>
            </td>
            <td>
               <table style={displayStyle}>
                  <tbody>
                     <tr><td>Name:</td><td>{person.name}</td></tr>
                     <tr><td>Age:</td><td>{person.age}</td></tr>
                     {person.contact.map((email, index) => {
                        return <tr key={index}><td>Email:</td><td>{email.email}</td></tr>
                     })}
                  </tbody>
               </table>
            </td>
         </tr>
      </table>
   </>)
}
export default App;

Lance

By: Lance

Hi, I'm Lance Raney, a dedicated Fullstack Developer based in Oklahoma with over 15 years of exp

Read more...