skip to Main Content

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


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

    Login or Signup to reply.
  2. The objects in the array are mutated (the array itself doesn’t change) because that’s exactly what you’re doing here:

    person.number = newPerson.number
    

    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:

    setNewName('')
    setTelNumber('')
    

    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:

    My intention was to create a new array with this map function and then calling setPersons()

    If that’s your intent then do that. For example:

    setPersons(persons.map(person => (
      person.id === newPerson.id ?
        { ...person, number: newPerson.number } : person
    )));
    

    Some key differences here are:

    1. State is not mutated. For objects which need a new property value set, a new object instance is returned with all of the properties of the existing instance plus the one updated one.
    2. The .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 of undefined values, which is also ignored.)
    3. That new array of objects is passed to setPersons to update state.
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search