0

I've made a basic phonebook application for an online exercise that allows you to save people's names and phone numbers to a phone book. There is also a search input that allows you to filter results based on name. It all works, however, one weird thing that I can't figure out is that the filter is always delayed by one character, the state isn't updated immediately when the user inputs a character into the search bar?

For example:

if I type 'J' into the search it will filter for ''

if I then type 'a' so now 'Ja' is in the search bar it will filter for 'J'

if I then backspace the 'a' so now 'J' is in the search bar it will filter for 'Ja'

and so on... the filter is always one step behind.

I'm new to state's and can't seem to fix it. Can anyone help me out? thanks. (also if there is any other constructive critique to my code that would improve it I'm very open and appreciative of it)

my Code:

const phonebook = [
  {
    id: 1,
    details: {
      name: 'Jim',
      number: '55 748 192'
    }
  },
  {
    id: 2,
    details: {
      name: 'Jason',
      number: '55 111 192'
    }
  },
  {
    id: 3,
    details: {
      name: 'Jonesy',
      number: '55 983 122'
    }
  },
  {
    id: 4,
    details: {
      name: 'Jack',
      number: '0408 729 000'
    }
  },

]


const App = (props) => {

  const [bookData, setBookData] = useState(props.props)
  const [newName, setNewName] = useState('')
  const [newNumber, setNewNumber] = useState('')
  const [theFilter, setTheFilter] = useState('')
  const [namesToShow, setNamesToShow] = useState(bookData.map(person => person.details))

  const [errorMessage, setErrorMessage] = useState('')

  console.log('the filter', theFilter, namesToShow)

  const addNumber = (event) => {
    event.preventDefault()
    const numberObject = {
      id: bookData.length + 1,
      details: {
        name: newName,
        number: newNumber,
      }
    }
    if(numberObject.details.name !== "" && numberObject.details.number !== '') {
      setBookData(bookData.concat(numberObject))
      setNewName('')
      setNewNumber('')
      setErrorMessage('')
    } else {
      if(numberObject.details.name === "" && numberObject.details.number === "") {
        setErrorMessage('You must enter a name and number!!')
      } else if(numberObject.details.number === "") {
        setErrorMessage('You must enter a number!!')
      } else {
        setErrorMessage('You must enter a name!!!')
      }
    }
  }

  const handleFilterChange = (event) => {
    setTheFilter(event.target.value)
    console.log(theFilter)
    if(theFilter === '') {
      setNamesToShow(bookData.map(person => person.details))
      console.log('help')
    } else {
      console.log('bookData', bookData.map(person => person.details))
      setNamesToShow(bookData.map(person => person.details).filter(person => person.name.includes(theFilter)))
      console.log('filter', theFilter)
      console.log('potential namestoshow?', bookData.map(person => person.details).filter(person => person.name.includes(theFilter)))
      console.log('namestoshow', namesToShow)
    }
  }

  const handleNameChange = (event) => {
    setNewName(event.target.value)
  }

  const handleNumberChange = (event) => {
    setNewNumber(event.target.value)
  }

  return (
    <>
      <h1>Phonebook</h1>
      <div>
        <input value={theFilter} onChange={handleFilterChange} placeholder='Search for a name' />
        {namesToShow.map(person => (
          <p>{person.name}: {person.number}</p>
          ))}
      </div>
      <form>
        <input value={newName} onChange={handleNameChange} placeholder='Enter a name...'/>
        <input value={newNumber} onChange={handleNumberChange} placeholder='Enter a number...'/>
        <button type='submit' onClick={addNumber}>Save</button>
      </form>
      <div>
        {errorMessage}
      </div>
    </>
  )
}



ReactDOM.render(
  <App props={phonebook} />,
  document.getElementById('root')
)
  • 1
    Does this answer your question? [Why calling react setState method doesn't mutate the state immediately?](https://stackoverflow.com/questions/30782948/why-calling-react-setstate-method-doesnt-mutate-the-state-immediately) State updates are asynchronous so in `handleFilterChange` when you `setTheFilter(event.target.value)` then later in the same function try to use `theFilter`, `theFilter` has the value from the *current* render cycle, not that *next* one queued up. – Drew Reese May 03 '20 at 02:52
  • 1
    Either use the value `event.target.value` within the function to filter on, or use an `useEffect` hook to appropriately respond to `theFilter` state value updating. – Drew Reese May 03 '20 at 02:57
  • excellent thanks, that makes sense. I switched out theFilter for event.target.value within handleFilterChange and works perfectly. – CodesColonel May 03 '20 at 05:21

1 Answers1

3

This is because useState updates is batched. When you set a new state it is not set immediately. So the best approach in a functional component would be to use a useEffect hook that depends on this variable. For example:

useEffect(() => {
  doSomeJob(theFilter);
}, [theFilter])

Where [theFilter] is an array of variables which change should fire that effect.

Alex Khristo
  • 477
  • 2
  • 8