简体   繁体   English

反应setState不更新精简数组

[英]React setState not updating reduced array

I have a pretty simple custom component: two select lists with buttons to move the options from the available (left) list to the selected (right) list. 我有一个非常简单的自定义组件:两个带有按钮的select列表,用于将options可用 (左)列表移动到选定 (右)列表。 Naturally, the moved element should no longer show up on the list it was moved from. 自然,移动的元素不应再出现在从其移动的列表中。 Though both buttons successfully add the element to the target, it doesn't remove from the source, because when I pass the reduced array of items to setState , the render still returns with the original list. 尽管两个按钮都成功地将元素添加到了目标中,但是它并没有从源中删除,因为当我将减少的项目数组传递给setState ,渲染仍然返回原始列表。

EDIT posting most of the component code for clarification. 编辑发布大多数组件代码以进行澄清。 The problem methods are the addItems and removeItems , where setState is called. 问题方法是addItemsremoveItems ,其中调用setState In both cases, whichever array property is being reduced/filtered is the one not updating; 在这两种情况下,要减少/过滤的任一数组属性都不更新。 the one being added to always updates properly. 被添加到其中的一个总是正确更新。

    ... 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>
                )
            })
        }
     }

(Sorry for any flawed formatting, posting was tricky) (很抱歉,如果格式有缺陷,发布会很棘手)

When this kicks off the new render, the selectedItems list is properly updated with the new item(s), but the availableItems is always the original array (yes I've ensured that the newAvailable array is properly filtered down), and even when I try 当这开始新的渲染时,selectedItems列表会使用新项正确更新,但是availableItems始终是原始数组(是的,我已经确保将newAvailable数组正确过滤掉),甚至当我尝试

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

I get the original availableItems array on the next render. 我在下一个渲染器上获得原始的availableItems数组。

Is there some nuance to returning similar-but-shorter arrays to state via setState ? 通过setState将相似但较短的数组返回到状态是否有些细微差别? I can't find anything referencing this behavior, and not sure what I'm missing. 我找不到任何引用此行为的信息,也不确定我丢失了什么。

Thanks 谢谢

This is the issue: 这是问题:

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

You are updating this.props.selectedItems here, but for availableItems: 您要在此处更新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
            });

Here, you do not directly update this.props.availableItems. 在这里,您没有直接更新this.props.availableItems。 The reason this matters is that when you call setState and render is triggered these methods: 这很重要的原因是,当您调用setState并渲染时,将触发以下方法:

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

are using this.props to return arrays, NOT this.state. 正在使用this.props返回数组,而不是this.state。 this.props.selectedItems has changed, and thus returns a different array, while this.props.availableItems has not changed. this.props.selectedItems已更改,因此返回了一个不同的数组,而this.props.availableItems未更改。

tl;dr - use this.state instead of this.props when passing the arrays to your renderOptionList method. tl; dr-在将数组传递给renderOptionList方法时,请使用this.state而不是this.props。

According to mozilla docs Array.prototype.filter should create new array, but described symptoms says that you just get 2 references to one array so there is no rerender. 根据mozilla的文档, Array.prototype.filter应该创建一个新数组,但是描述的症状表明您只对一个数组有2个引用,所以没有重新渲染。 So please try this 所以请尝试这个

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
});

编辑r40q9x3vxn

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM