Search code examples
javascriptreactjspreact

List childs are not beeing updated correctly? (React / Preact)


I have the following component

import {h, Component} from 'preact'
import {getPersons} from '../../lib/datalayer'
import Person from '../person'
import {SearchInput} from '../search'

export default class Persons extends Component {
  state = {
    allPersons: [],
    persons: [],
    search: ''
  }

  async fetchData () {
    try {
      const allPersons = await getPersons()
      this.setState({allPersons: allPersons.slice(), persons: allPersons.slice()})
    } catch (error) {
      ....
    }
  }

  constructor (props) {
    super(props)
    this.state = {
      allPersons: [],
      persons: [],
      search: ''
    }
    this.fetchData()
  }

  onSearchInput = (search) => {
    if (search === '') {
      this.setState({search: search, persons: this.state.allPersons.slice()})
    } else {
      const persons = this.state.allPersons.filter(p => p.name.toLowerCase().includes(search.toLowerCase())).slice()
      this.setState({search: search, persons: persons)})
    }
  }

  render () {
    const {persons} = this.state
    return (
      <div>
        <SearchInput onInputChange={this.onSearchInput} placeHolder={'filter: name'} />
        {persons.map(p => <Person person={p} />)}
      </div>
    )
  }
}

The page renders a list of Persons and it has a filter on top. The filter seems to work fine, I tested it by doing a console.log of the results are just fine

The problem is that, if my list contains the objects:

[{name: 'thomas'}, {name: 'john'}, {name: 'marcus'}, {name: 'usa'}]

And I write in the search input: 'us'

The filter works fine and the result is:

[{name: 'marcus'}, {name: 'usa'}] \\ (the expected result)

In the page this objects are rendered

[{name: 'thomas'}, {name: 'john'}] \\ (wrong, this are the two first elements of the list)

If I search: 'joh'

The filter's result is

[{name: 'john'}] \\ (this is fine)

And the page renders only

[{name: 'thomas'}] \\ (the first element in the list)

It looks like the amount of elements that are rendered it's fine, but the content of the childs of the list is not beeing re-rendered.

Whats's wrong with my code?


Solution

  • React uses keys on the children of a list to determine which items changed and which of them remains the same. Since you have not specified a key on person, it takes index to be the key.

    When index is key, you can see how shortening the list to two items, shows up the first two items in the list (the other indices are now missing). To get around this, you have to give a unique identifier on the person as key.

    From your object, assuming name is unique (it usually isn't):

     {persons.map(p => <Person person={p} key={p.name} />)}
    

    Why are keys necessary - Docs