react.js typescript spread(...) adding only the last element

Viewed 284

I'm trying to add an element in an array in react.js

this.state.chat_list.map(elem => {
  if (
    elem.name
      .toLowerCase()
      .indexOf(this.props.searching_username.toLowerCase()) !== -1
  ) {
    this.setState(
      { searched_chat_list: [...this.state.searched_chat_list, elem.name] },
      () => {
        console.log(this.state.searched_chat_list);
      }
    );
  }
});

Here is code.

The problem is It only adds the last element from the result

Assume I have a,ab,c,d,aad and if I search with a it will return only aad instead of a,ab,aad.

How can I resolve this?

2 Answers

Surely you want something like this:

this.setState(prev => ({
    searched_chat_list: [
        ...prev.searched_chat_list,
        prev.chat_list.filter(el =>
            el.name
                .toLowerCase()
                .includes(this.props.searching_username.toLowerCase())
        ),
    ],
}))

There are several things that I corrected:

  • If you're filtering, you should use filter not map (and if you're trying to do a for-each, then use forEach not map. map is for mapping an array to another array, which is not what you're doing here.

  • I passed a function to setState so that I wasn't using stale state values. The reason your setState was only setting the last value is because you kept overwriting the previous value on every iteration of the loop.

For example, imagine you did something like this:

// State
const state = {
  myArray: [1, 2]
}

// Let's add this array to the one in state
[3, 4, 5, 6].forEach(num => {
  setState({
    // state.myArray is always [1, 2]
    myArray: [...state.myArray, num]
  })
})

What do you think the result here would be? You expect [1, 2, 3, 4, 5, 6], but the result is actually [1, 2, 6].

State is only updated on re-render, so the value of state.myArray is ALWAYS [1, 2], even on the last iteration of the loop - because the component hasn't re-rendered yet, so state hasn't updated.

Basically you're doing this:

  setState({myArray: [1, 2, 3]})
  setState({myArray: [1, 2, 4]})
  setState({myArray: [1, 2, 5]})
  setState({myArray: [1, 2, 6]})

Now can you see why only the last item is added to the array?

If your new state value depends on the previous state value, you should always pass a function to setState:

// Let's pass a function to setState this time
[3, 4, 5, 6].forEach(num => {
  setState(prev => ({
    // prev is always the most up-to-date state value
    myArray: [...prev.myArray, num]
  }))
})

The prev value is always the most up-to-date state value, so the result here would be [1, 2, 3, 4, 5, 6], which is what we wanted, although in your case, you didn't need to iterate the array anyway.

Simplify it, Filter First than add:

const newList = this.state.chat_list.filter(
  elem =>
    elem.name
      .toLowerCase()
      .indexOf(this.props.searching_username.toLowerCase()) !== -1
);
this.setState(
  {
    searched_chat_list: [...this.state.searched_chat_list, ...newList]
  },
  () => console.log(this.state.searched_chat_list)
);
Related