Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing an item causes React to remove the last DOM node instead of the one associated with that item

I was trying to animate list insertion and removal with ReactCSSTransitionGroup, but the removal animation always animates only the last item of the list instead of the one that's being removed.

Here's a jsbin to illustrate this problem. Try pressing the "Add" button to verify that the insertion animation indeed works as expected, then click on the "x" besides any item to see the problem where the last item of the list is animated instead of the one you tried to remove.

Did I do something wrong while setting up the TransitionGroup or am I missing something in the CSS transition definitions?

like image 502
user14412 Avatar asked May 22 '15 22:05

user14412


People also ask

How do I remove an item from a react list?

To delete an item from list with React and JavaScript, we can use some array methods. to create the items state with useState . Then we define the deleteItem function that takes the index of the item to delete and returns a function that calls setItems with (items) => items. filter((_, i) => i !==


1 Answers

You're experiencing this problem because you're using index as your key:

let nodes = items.map((item, index) => {
  let idx = index
  return (<Item key={index} value={item} index={index} _delete={this._onDelete}/>)
})

React uses the key property during virtual DOM diffing to figure out which element was removed, but indexes will never serve this purpose sufficiently.

Consider this example: you start off with the following array, which results in the following DOM structure:

const arr = [2, 4, 6, 8];

<li key={0}>2</li>
<li key={1}>4</li>
<li key={2}>6</li>
<li key={3}>8</li>

Then imagine you remove the element at index 2. You now have the following array, and the following DOM structure:

const arr = [2, 4, 8];

<li key={0}>2</li>
<li key={1}>4</li>
<li key={2}>8</li>

Notice that the 8 now resides in index 2; React sees that the difference between this DOM structure and the last one is that the li with key 3 is missing, so it removes it. So, no matter which array element you removed, the resulting DOM structure will be missing the li with key 3.

The solution is to use a unique identifier for each item in the list; in a real-life application, you might have an id field or some other primary key to use; for an app like this one, you can generate a incrementing ID:

let id = 0;
class List extends Component {
  constructor() {
    this.state = {
      items: [{id: ++id, value: 1}, {id: ++id, value: 2}]
    }

    // ...
  }

  _onClick(e) {
    this.state.items.push({id: ++id, value: Math.round(Math.random() * 10)})
    this.setState({items: this.state.items})
  }

  // ...

  render() {
    let items = this.state.items
    let nodes = items.map((item, index) => {
      let idx = index
      return (<Item key={item.id} value={item.value} index={index} _delete={this._onDelete}/>)
    })

    // ...
  }
}

Working example: http://jsbin.com/higofuhuni/2/edit

like image 126
Michelle Tilley Avatar answered Sep 18 '22 21:09

Michelle Tilley