Есть список, каждый элемент которого является компонентом (Item). Список строится из state родителя (List). Задача в том, чтобы при клике в Item, из state List удалялся соответствующий элемент. Я сделал это так: передал через props onClose функцию удаления в Item и вызывал ее уже оттуда.
И все это даже работало до тех пор, пока не потребовалось вызывать onClose после изменения state Item (внутри setTimeout). Если раскомментировать строку 27 в примере на jsfiddle, то странным образом state Item'а, находящегося под тем, который мы закрываем, меняется.
Живой пример: https://jsfiddle.net/69z2wepo/114657/
Код:
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
height: 0,
opacity: 0
};
}
componentDidMount() {
setTimeout(() => {
this.setState({height: '500'}, () => {
setTimeout(() => {
this.setState({opacity: 1})
},100)
})
},100)
}
close() {
this.setState({opacity: 0}, () => {
setTimeout(() => {
this.setState({height: 0}, () => {
setTimeout(() => {
// Вот тут и начинается проблема
// this.props.onClose();
},100)
})
},100)
})
}
render() {
return (
<div className="item-wrapper" style={{maxHeight: this.state.height + 'px'}}>
<div className="item" onClick={this.close.bind(this)} style={{opacity: this.state.opacity}}>
{this.props.title}
</div>
</div>
);
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{title: 'zero'},
{title: 'one'},
{title: 'two'},
{title: 'three'},
{title: 'four'},
{title: 'five'},
{title: 'six'},
{title: 'seven'},
{title: 'eight'},
{title: 'nine'},
{title: 'ten'},
]
};
}
// Удаление элемента
delete(item) {
let newItems = this.state.items;
if (newItems.indexOf(item) > -1) {
newItems.splice(newItems.indexOf(item), 1);
this.setState({items: newItems});
}
}
render() {
return (
<div className="list">
{ this.state.items.map((item, i) => {
return (<Item key={i} title={item.title} onClose={this.delete.bind(this, item)} />)
})}
</div>
);
}
}
ReactDOM.render(
<List />,
document.getElementById('root')
);
Буду рад выслушать мнения о том, как это починить. Заранее благодарен.
P.S. Предвосхищая вероятные советы, скажу сразу, что про существование react-transition-group я знаю, но текущий вопрос не в выборе инструментов.
Использовать в качестве key для компонентов порядковый номер не очень хорошая затея.
{this.state.items.map(item=> (
<Item
key={item.title} // как нужно, либо другое св-во объекта
title={item.title}
onClose={this.delete.bind(this, item)}
/>)
))}
Давайте поймем, почему так:
Изначально наш state это 11 элементов, React добавляет каждому key, который равен порядковому индексу.
Грубо говоря у нас есть такая структура:
// представление данных, а не js объект
{
0: {title: 'zero'},
1: {title: 'one'}
}
React выводит все это дело и у себя запоминает, какой key у какого компонента
Далее, вы удаляете один из компонентов, что у нас получается? Из стейта теряется один объект, но что происходит с нашим массивом ключей? Это все так же [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], поменялась только длина. Следовательно, вид будет такой
{
0: {title: 'one'},
1: {title: 'two'}
}
Далее, React начинает рисовать компоненты. Наш первый компонент имеет opacity: 0, но какие props ему передадутся? Правильно, {title: 'one'}, потому что ключ не удалился, все так же есть компонент с key = 0.
Итого, мы удалил не компонент, а лишь данные, сам компонент остался, так как все еще присутствует его key
Решение:
Как я уже написал выше, добавьте в данные каждому объекту уникальный идентификатор, тогда react будет знать, что надо удалить не только данные, но и компонент, потому что key уже не будет.
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости