React: удаление элемента из state родителя

268
26 февраля 2018, 02:58

Есть список, каждый элемент которого является компонентом (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 я знаю, но текущий вопрос не в выборе инструментов.

Answer 1

Использовать в качестве 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 уже не будет.

READ ALSO
Сортировка массива по названию

Сортировка массива по названию

Есть массивЕго надо отсортировать так, чтоб порядок был следующий: Здравствуйте, Здравствуйте, Привет, Привет, Пока

241
Перед переносом появился � знак

Перед переносом появился � знак

Сайт на WP это виджет дополнение темыКодировки в норме что может быть?

286
Не доступен метод PUT

Не доступен метод PUT

Всем привет! Столкнулся с проблемой, что у меня не доступен метод PUTЯ пишу некоторый Api на PHP Slim

211