React setState不更新简化数组

时间:2018-06-07 19:31:29

标签: javascript reactjs typescript

我有一个非常简单的自定义组件:两个select列表,其中包含用于将options可用(左)列表移动到所选的按钮strong>(右)列表。当然,移动的元素不应再显示在它移动的列表中。虽然两个按钮都成功地将元素添加到目标,但它不会从源中删除,因为当我将减少的项目数组传递给setState时,渲染仍然会返回原始列表。

编辑发布大部分组件代码以便澄清。问题方法是addItemsremoveItems,其中调用了setState。在这两种情况下,减少/过滤的数组属性都是不更新的;正在添加的正常更新。

    ... imports
    interface JoinedListState {
        availableItems: ListItem[]
        selectedItems: ListItem[]
    }

    export class JoinedList extends React.Component<JoinedListState, any>{
    // Create new arrays of the proper available and selected then set the new 
    // state
        private addItems(newItems: ListItem[]) {
            let oldSelected =  this.props.selectedItems;
            oldSelected.push.apply(oldSelected, newItems);
            let newSelected = oldSelected.sort((a, b) => {
                let nameA = a.value.toUpperCase();
                let nameB = b.value.toUpperCase();
                if (nameA < nameB) {
                    return -1
                }
                return 1
            });
            let newAvailable = this.props.availableItems
                .slice(0) // updated on recommendation of Sasha Kos
                .filter((item) => {
                    return newItems.findIndex(i => i.id == item.id) == -1
                });
            this.setState({
                availableItems: newAvailable,
                selectedItems: newSelected
            });
        }
        // Create new arrays of the proper available and selected then set the 
        //new state
        private removeItems(removedItems: ListItem[]) {
             .. same approach as addItems
            let newSelected = this.props.selectedItems.filter((item) => {
            // return only the items whose id does not exist on the newly 
            //removed items list
                return removedItems.findIndex(i => i.id == item.id) == -1
            })
            this.setState({
                availableItems: newAvailable,
                selectedItems: newSelected
            })
        }

        // Get the selected items by querying the DOM and send them to function 
        // to update state
         addSelected(event: React.FormEvent<HTMLButtonElement>) {
                // Code removed for brevity:  uses the event object to find the 
                //selected objects and builds a ListItem array called 'selected' 
                //to pass to addItems
                this.addItems(selected)
            }  

            removeSelected(event: React.FormEvent<HTMLButtonElement>) {
                // Code removed for brevity:  uses the event object to find the 
                //selected objects and builds a ListItem array called 'selected' 
                //to pass to addItems
                this.removeItems(selected)
            }

        render() {
            let aItems = this.renderOptionList(this.props.availableItems),
                sItems = this.renderOptionList(this.props.selectedItems);
            return (
                <div className='joined-list-container'>
                    <select key='available_list' className='available-list form- 
   control' multiple>
                        {aItems}
                    </select>
                    <span className='button-container'>
                        <button key='button1' className='btn btn-success' 
    onClick={this.addSelected.bind(this)}>
                            <span className='glyphicon glyphicon-chevron-right'> 
   </span>
                        </button>
                        <button key='button2' className='btn btn-danger' 
    onClick={this.removeSelected.bind(this)}>
                            <span className='glyphicon glyphicon-chevron-left'> 
   </span>
                        </button>
                    </span>
                    <select key='selected_list' className='selected-list form- 
   control' multiple>
                        {sItems}
                    </select>
               </div>
            )
        }

        renderOptionList(items: ListItem[]) {
            return items.map((item, idx) => {
                let key = `${item.value}_${idx}`
                return (
                    <option value={item.id} key={key}>{item.value}</option>
                )
            })
        }
     }

(对不起任何有缺陷的格式,发帖很棘手)

当这启动新渲染时,selectedItems列表会使用新项正确更新,但availableItems始终是原始数组(是的,我确保newAvailable数组已正确过滤()),甚至当我尝试

    this.setState({
        availableItems: [],
        selectedItems: newSelected
    })

我在下一个渲染中获得原始的availableItems数组。

通过setState将类似但更短的数组返回状态是否有一些细微差别?我找不到任何引用这种行为的东西,也不确定我错过了什么。

由于

2 个答案:

答案 0 :(得分:2)

这就是问题所在:

let oldSelected =  this.props.selectedItems;
        oldSelected.push.apply(oldSelected, newItems);

您正在此处更新this.props.selectedItems,但是对于availableItems:

let newAvailable = this.props.availableItems
            .slice(0) // updated on recommendation of Sasha Kos
            .filter((item) => {
                return newItems.findIndex(i => i.id == item.id) == -1
            });

在这里,您不直接更新this.props.availableItems。这很重要的原因是,当你调用setState并且渲染时会触发这些方法:

let aItems = this.renderOptionList(this.props.availableItems),
            sItems = this.renderOptionList(this.props.selectedItems);

使用this.props返回数组,而不是this.state。 this.props.selectedItems已更改,因此返回不同的数组,而this.props.availableItems未更改。

tl; dr - 在将数组传递给renderOptionList方法时使用this.state而不是this.props。

答案 1 :(得分:1)

根据mozilla docs,Array.prototype.filter应该创建新的数组,但是描述的症状表明你只获得了对一个数组的2个引用,因此没有重新呈现。所以请试试这个

let newAvailable = this.props.availableItems
  .slice(0) /* clones your array */
  .filter((item) => {
    return newItems.findIndex(i => i.id == item.id) == -1
  });

this.setState({
    availableItems: newAvailable,
    selectedItems: newSelected
});

Edit r40q9x3vxn