Search code examples
javascriptreactjsjsonserverreact-hooks

my state keeps adding empty elements into its array


My setter, for SetPersons, seems to keep adding empty elements into its array for no apparent reason. I am trying to make my phone number list update instantaneously when I update a user's number but it sticks with the old number. Whenever I apply the logic to possibly make the number update instantly, it adds empty elements into my array instead and does not update instantly. Here is the code for each separate component:

App.js

import { useEffect, useState, useRef} from 'react'
import PersonForm from './components/PersonForm'
import Display from './components/Display'
import Filter from './components/Filter'
import phoneService from './services/information'

const App = () => {
  const [errorMessage, setErrorMessage] = useState('')
  const [persons, setPersons] = useState('')
  const [newName, setNewName] = useState('')
  const [newNumber, setNewNumber] = useState('')
  const [filter, setFilter] = useState('')
  const [filterChecker, setFilterChecker] = useState('')
  
  useEffect(() => {
    phoneService.getAll()
    .then(people => setPersons(people))
  }, [])

  const handleSubmit = (e) => {
    e.preventDefault();
    const copy = [...persons]
    const foundNameOrNot = persons.some(el => el.name === newName)
    const foundNumberOrNot = persons.some(el => el.number === newNumber)
    const eachPersonsID = persons.map(person => person.id)
    const newObj = {
      name: newName,
      number: newNumber,
      id: Math.max(eachPersonsID) + 1
    }
    if (foundNameOrNot) {
      if(window.confirm(`${newName} is added to phonebook, replace the old number with a new one.`)) {
        const indexFinder = persons.filter(person => person.name === newName)
        newObj.id = indexFinder[0].id
        copy[indexFinder[0].id] = newObj
        phoneService.update(indexFinder[0].id, newObj)
        .then(res => {
          setErrorMessage(`Successfully updated ${newName}s number`)
          setPersons([copy])})
        .catch(err => {
          setErrorMessage(`Information on ${newName} has already been removed`)
          setFilterChecker(filterChecker.filter(filter => filter !== newName))})
      }
    } else if (foundNumberOrNot) {
      alert(`${newNumber} belongs to someone else`)
    } else {
      phoneService.create(newObj).then(res => setPersons([...persons, newObj]))
    }
  }

  const handleFilter = (e) => {
    console.log(persons)
    setFilter(e.target.value)
    const allNames = persons.map(person => person.name)
    setFilterChecker(allNames.filter(name => name.toLowerCase().indexOf(filter.toLowerCase()) !== -1))
  }

  return (
    <div>
      <h1 className='error'>{errorMessage ? errorMessage : ''}</h1>
      <h2>Phonebook</h2>
      <Filter filter={filter} handleFilter={handleFilter}/>
      <h2>add a new</h2>
      <form onSubmit={handleSubmit}>
        <PersonForm newName={newName} newNumber={newNumber} setNewName={setNewName} setNewNumber={setNewNumber}/>
        <div>
          <button type="submit">add</button>
        </div>
      </form>
      <h2>Numbers</h2>
      <Display filterChecker={filterChecker} persons={persons} setPersons={setPersons} setFilterChecker={setFilterChecker} errorMessage={errorMessage} setErrorMessage={setErrorMessage} />
    </div>
  )
}

export default App

Display.js

import phoneService from '../services/information'
import axios from 'axios'
import { useState } from 'react'

const handleDelete = (i, persons, setPersons, name2, setFilterChecker, errorMessage, setErrorMessage) => {
    if (window.confirm(`delete ${name2} ?`)) {
        const newArrayOfPeople = persons.filter(person => person.name !== name2)
        const newArrayOfNames = newArrayOfPeople.map(person => person.name)
        setFilterChecker(newArrayOfNames)
        setPersons(newArrayOfPeople)
        phoneService.remove(persons[i].id)
        setErrorMessage(`You have successfully deleted ${name2} from the list.`)
        setTimeout(() => {
            setErrorMessage(null)
        }, 5000)
    }
}

const Display = ({filterChecker, persons, setPersons, setFilterChecker, errorMessage, setErrorMessage}) => {
    const copy = [...persons]
    const findNumbers = []
    const findNames = []
    for (let j = 0; j < copy.length; j++) {
        if (copy[j]?.name && !findNames.includes(copy[j]?.name)) {
            findNames.push(copy[j].name)
        }
    }
    for (let i = 0; i < copy.length; i++) {
        if (copy[i]?.number && !findNumbers.includes(copy[i]?.number)) {
            findNumbers.push(copy[i]?.number)
        }
    }
  if (filterChecker) {
    return (
      findNames.map((name, i) => <div><nobr key={name}>{name} {findNumbers[i]}</nobr> <button onClick={() => handleDelete(i, persons, setPersons, name, setFilterChecker, errorMessage, setErrorMessage)}>delete</button></div>)
      )
  } else {
    return ''
  }

}

export default Display

Filter.js

const Filter = ({filter, handleFilter}) => {
    return <p>filter shown with <input value={filter} onChange={handleFilter}/></p>
  }
  
  export default Filter

PersonForm.js

import axios from 'axios';
import phoneService from '../services/information'

const PersonForm = ({newName, newNumber, setNewName, setNewNumber}) => {

  return (
    <div>
      name: <input value={newName} onChange={(e) => setNewName(e.target.value)}/>
      number: <input value={newNumber} onChange={(e) => setNewNumber(e.target.value)}/>
    </div>
  )
}

export default PersonForm

information.js

import axios from "axios"
const baseUrl = 'http://localhost:3002/numbers'

const getAll = () => {
  const request = axios.get(baseUrl)

  return request.then(response => response.data)
}

const create = newObject => {
  const request = axios.post(baseUrl, newObject)
  return request.then(response => response.data)
}

const update = (id, newObject) => {
  const request = axios.put(`${baseUrl}/${id}`, newObject)
  return request.then(response => response.data)
}

const remove = (id) => {
  const request = axios.delete(`${baseUrl}/${id}`)
  return request.then(res => res.data)
}

export default { getAll, create, update, remove, baseUrl }

My json-server "db.json"

{
  "numbers": [
    {
      "name": "uvaldo",
      "number": "20",
      "id": 6
    },
    {
      "name": "uvaldo zumaya",
      "number": "24",
      "id": 7
    },
    {
      "name": "uvaldo zumaya d",
      "number": "26",
      "id": 8
    }
  ]
}

Whenever I try to update "uvaldo" phone number to 22, it just stays at 21 then throws errors, this picture is before I hit "add" to avoid the program crashing...

https://i.sstatic.net/CD2rh.png


Solution

  • There are issues with the logic where you update persons.

    Try this instead:

      const handleSubmit = (e) => {
    ...
            const indexFinder = persons.findIndex(person => person.name === newName)
            newObj.id = persons[indexFinder].id
            copy[indexFinder] = newObj
            phoneService.update(persons[indexFinder].id, newObj)
            .then(res => {
              setErrorMessage(`Successfully updated ${newName}s number`)
              setPersons(copy)})
    ...
      }
    

    The reason why it was adding empty elements is that the index used to place newObj was greater than the size of the array:

    copy[indexFinder[0].id] = newObj
    

    Also note that person.id does not necessarily mean that they are in that index of the array persons.