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"}]
}
The initialState is stored as a state variable.
Our purpose is to modify the contact array contained in the person object.
There are a few steps to adding a new element into a state array.
Now let's take a look at how to add a new email address to the contact array.
In this method, we follow four steps for clarity. For a short version of this, check example 2.
const addEmail = () => {
setPerson((prevPerson) => {
let contact = [...prevPerson.contact]
contact = [...contact, {email: Math.random()+"@learnbestcoding.com"}]
let newPerson = {...prevPerson, contact:contact}
return newPerson
})
}
This is the shorten form of Example 1.
const addEmail = () => {
setPerson((prevPerson) => {
return {...prevPerson, contact: [...prevPerson.contact, {email: Math.random()+"@learnbestcoding.com"}]}
})
}
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}
})
}
The React state should not be mutated directly. The below code produces unexpected results and has multiple issues.
//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;