The easiest way to update a nested object stored in the Reactjs state is to use the spread operator.
I will explain it with several examples.
We have a demo Reactjs app that handles a simple personal information form. The form contains the person's information, including address and email address. The address is nested in the person object, and the state and zip are nested in the address object.
interface person {
name: string
age: string
address:{
street: string,
city: string,
stateZip:{
state: string,
zip: string
}
}
contact: {email: string}[]
}
An instance of the person object to use as the initial state
let initialState: person = {
name: "Lance",
age: "36",
address: {
street: "100 Some street",
city: "Edmond",
stateZip:{
state: "Oklahoma",
zip: "73034"
}
},
contact: [{email: "abc@learnbestcoding.com"}, {email: "aab@learnbestcoding.com"}]
}
This person object is stored in the state as
const [person, setPerson] = useState
1. Updating Object properties stored in the state #
The property's name and age belong to the person and are not nested. So how do we update them?
//This doesn't work
const updatePersonName = (name: string) => {
person.name = value
setPerson(person)
}
The above code will not work. That is because we mutate the state directly, which violates Reactjs principles. When the React state is mutated directly, Reactjs does not re-render the reflect the state change. The correct way to update the name and age is to use the spreader operator and the useState-provided setter method. With the spreader method, we create a new person object and assign that to the state. No mutation happens. Note that we read the person state from the prevPerson. That way, we are guaranteed to receive the up-to-date person state from React.
const updatePerson = (name: string) => {
setPerson((prevPerson) => {
return {...prevPerson, [name]: value}
})
}
2. Updating nested Object properties stored in the state #
The address is a nested object residing in the person object. The representation is person -> address. Below is how to update the nested object address. Again we create a new object named newPerson, mutate it, and assign it to the state. No direct state mutations happens.
const updateAddress = (event: HTMLInputElement) => {
const {name, value} = event
setPerson((prevPerson) => {
const newPerson = {...prevPerson}
if(name === "street" || name === "city"){
newPerson.address[name] = value
}
return newPerson
})
}
3. Updating deeply nested Object properties stored in the state #
The stateZip is a nested object residing in the address object. The representation is person -> address -> stateZip.
const updateStateZip = (event: HTMLInputElement) => {
const {name, value} = event
setPerson((prevPerson) => {
const newPerson = {...prevPerson}
if(name === "state" || name === "zip"){
newPerson.address.stateZip[name] = value
}
return newPerson
})
}
4. Updating nested Object arrays stored in the state #
The contact is an object array nested in the person object. The below code snippet shows how to add elements to an object array nested in an object. Note that we spread the previous person state and previous contact and append the new email object to the spread contact array.
const addEmail = () => {
setPerson((prev) => {
return {...prev, contact: [...prev.contact, {email: Math.random()+"@learnbestcoding.com"}]}
})
}
Demo Reactjs code to illustrate updating nested objects and arrays #
Below is an example of updating nested objects and arrays.
import { useState } from 'react';
export const App = () => {
interface person {
name: string
age: string
address:{
street: string,
city: string,
stateZip:{
state: string,
zip: string
}
}
contact: {email: string}[]
}
let initialState: person = {
name: "Lance",
age: "36",
address: {street: "100 Some street", city: "Edmond", stateZip:{state: "Oklahoma", zip: "73034"}},
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: 250, "padding": 10}
const [person, setPerson] = useState<person>(initialState)
const updatePerson = (event: HTMLInputElement) => {
const {name, value} = event
if(name === "name" || name === "age")
{
setPerson((prevPerson) => {
return {...prevPerson, [name]: value}
})
}
if(name === "street" || name === "city")
{
setPerson((prevPerson) => {
const newPerson = {...prevPerson}
newPerson.address[name] = value
return newPerson
})
}
if(name === "state" || name === "zip")
{
setPerson((prevPerson) => {
const newPerson = {...prevPerson}
newPerson.address.stateZip[name] = value
return newPerson
})
}
}
const addEmail = () => {
setPerson((prev) => {
return {...prev, contact: [...prev.contact, {email: Math.random()+"@learnbestcoding.com"}]}
})
}
return(
<>
<table>
<tr>
<td>
<table style={inputStyle}>
<tbody>
<tr><td>Name:</td><td><input type="text" name="name" value={person.name} onChange={(e) => updatePerson(e.target)}/></td></tr>
<tr><td>Age:</td><td><input type="number" name="age" value={person.age} onChange={(e) => updatePerson(e.target)}/></td></tr>
<tr><td>Street:</td><td><input type="text" name="street" value={person.address.street} onChange={(e) => updatePerson(e.target)}/></td></tr>
<tr><td>City:</td><td><input type="text" name="city" value={person.address.city} onChange={(e) => updatePerson(e.target)}/></td></tr>
<tr><td>State:</td><td><input type="text" name="state" value={person.address.stateZip.state} onChange={(e) => updatePerson(e.target)}/></td></tr>
<tr><td>Zip:</td><td><input type="number" name="zip" value={person.address.stateZip.zip} onChange={(e) => updatePerson(e.target)}/></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>
<tr><td>Street:</td><td>{person.address.street}</td></tr>
<tr><td>City:</td><td>{person.address.city}</td></tr>
<tr><td>State:</td><td>{person.address.stateZip.state}</td></tr>
<tr><td>Zip:</td><td>{person.address.stateZip.zip}</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;