this is my first question in this forum. I hope I can describe my problem correctly.
I have a state hook called persons:
const [persons, setPersons] = useState([])
My goal is to re-render the "persons" array after making a PUT Request. My Code works as intended, but I don’t understand why the "persons" array gets automatically reassigned even though I don’t use the setPersons() method.
if(window.confirm(`${newPerson.name} is already added to the phonebook, replace the old number with a new one?`))
{
console.log('update person:',newPerson)
personsService.updatePerson(newPerson)
.then(response => console.log(`${response.name}'s number has been updated`))
//update persons list locally
persons.map(person=>{
person.id === newPerson.id ? person.number = newPerson.number : person //why does this code work without setPersons??
})
//when I don't use this map function, the array doesn't get automatically re-assigned
Thanks in advance!
My full code:
import { useState, useEffect } from 'react'
import Filter from './components/Filter'
import PersonForm from './components/PersonForm'
import Persons from './components/Persons'
import personsService from './services/persons'
const App = () => {
const url = 'http://localhost:3001/persons'
const [persons, setPersons] = useState([])
//useEffect is executed after all components have been rendered
//this is why console.log("Execute the other codes...") was executed before console.log("fetching data from the server...")
//despite the code order
useEffect(()=>{
console.log("fetching data from the server...")
personsService.getPersons()
.then(response=>{
console.log("Data have been fetched successfully!")
console.log("Data: ", response)
console.log("Update Persons state...")
setPersons(response)
})
},[])
console.log("Execute the other codes...")
console.log("Initial state of persons state is: ",persons)
const [newName, setNewName] = useState('')
const [telNumber, setTelNumber] = useState('')
const [filter, setFilter] = useState('')
//Event handler
const onChangeHandler = (event) =>{
setNewName(event.target.value)
}
const deleteHandler = (id,name) =>{
console.log('Auaaa',id)
if(window.confirm(`Delete ${name}?`))
{
personsService.deletePerson(id)
const arrayAfterDeletion = persons.filter(person=>person.id !== id)
setPersons(arrayAfterDeletion)
}
}
const telChangeHandler = (event) =>{
setTelNumber(event.target.value)
}
const filterChangeHandler= (event) =>{
setFilter(event.target.value)
}
const onClickHandler = (event)=>{
event.preventDefault()
const newPerson = {id:newName, name:newName, number:telNumber}
//check persons list, whether it contais the same person (same name & number)
let containsTheSamePerson = false
persons.forEach(person=>{
//doesn't make sense here if we compare the objects (always different because the id)
if(person.name === newPerson.name)
{
containsTheSamePerson = true
}
})
if(!containsTheSamePerson)
{
const newPersonList = persons.concat(newPerson)
//console.log('old list', persons)
//console.log('new list',newPersonList);
setPersons(newPersonList)
//push new person to the server
personsService.addNewPerson(newPerson)
.then(response => console.log(`${response.name} has been added to database`))
setNewName('')
setTelNumber('')
}else{
//alert(`${newPerson.name} is already added to the phonebook`)
if(window.confirm(`${newPerson.name} is already added to the phonebook, replace the old number with a new one?`))
{
console.log('update person:',newPerson)
personsService.updatePerson(newPerson)
.then(response => console.log(`${response.name}'s number has been updated`))
//update persons list locally
persons.map(person=>{
person.id === newPerson.id ? person.number = newPerson.number : person //why does this code work withour setPersons??
})
setNewName('')
setTelNumber('')
}
}
}
return (
<div>
<h2>Phonebook</h2>
<Filter value = {filter} onChangeHandler = {filterChangeHandler}/>
<h3>Add a new</h3>
<PersonForm nameValue={newName} nameChangeHandler={onChangeHandler}
numValue={telNumber} numChangeHandler={telChangeHandler}
buttonHandler={onClickHandler}/>
<h2>Numbers</h2>
<Persons list = {persons} filter = {filter} onClickDelete={deleteHandler}/>
</div>
)
}
export default App
2
Answers
persons
does not change; it remains the same array reference. But you modified the objects inside the array, which you are NOT supposed to do. You must not directly mutate your state in React, but it is also not going to check if you did so.When you then call
setNewName
, a rerender is queued and your function component is called again, this time using the modified objects to generate the output.The objects in the array are mutated (the array itself doesn’t change) because that’s exactly what you’re doing here:
This sets a new value to the
number
property of that object. So after doing this in a.map()
call (which is a mis-use of.map()
by the way, looping over an array to perform an operation should be with a loop or.forEach()
), that property is updated on those objects.Coincidentally the component also re-renders because you also update other state values:
So you’re mutating one state (which you should never do in React) and updating another state. The result is that the component re-renders and both states have new data. One was just updated incorrectly.
In a comment above you also mention:
If that’s your intent then do that. For example:
Some key differences here are:
.map()
callback returns objects so the.map()
operation itself produces a new array of objects. (In your code nothing is returned so.map()
produces an array ofundefined
values, which is also ignored.)setPersons
to update state.