卸载父元素时,react-bootstrap ModalTrigger 不会隐藏 [英] react-bootstrap ModalTrigger doesn't hide when the parent element is unmounted

查看:36
本文介绍了卸载父元素时,react-bootstrap ModalTrigger 不会隐藏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在对一系列项目使用 react-bootstrap 的 ModalTrigger 时遇到了一个奇怪的行为,当父项/所有者项被卸载时它不会消失.我们怀疑这与 React 的虚拟 DOM 和差异机制,和/或我们自己对 ModalTrigger 的滥用有关.

设置很简单:一个 Content 反应组件有一个状态,其中包含一个项目名称数组.它还有一个 onClick(name) 函数,可以通过 setState 从数组中删除该名称.在render中,它使用_.map来创建一堆Item react组件.

每个 Item 组件显示其名称和一个 ModalTrigger,其中包含一个标记为删除我"的按钮.单击按钮,它会打开模态;在模态中单击确定",它会执行对内容删除函数的回调.

当删除最后一个项目时它工作正常:最后一个 Item 组件被卸载,与它一起,ModalTrigger 及其相应的模态.

我们看到的有问题的行为是删除除最后一项以外的任何项目.该项目被删除但模态保持打开状态,而我天真地希望模态消失,因为父 ModalTrigger 消失了.不仅如此,当再次点击确定"时,列表中的下一项被移除,依此类推,直到模态恰好与最后一项相关联,此时点击确定"将最终隐藏它.

我们的集体预感是,这是由于overlayMixin 的_overlayTarget 是文档中的匿名元素造成的,因此不同的ModalTriggers 无法区分它们.因此,当父级卸载并且 React 查找 DOM 差异时,它会看到前一个触发器并说嘿,那可以工作".

这整个问题可以通过在代码中注释掉的项目的内部 _onClick() 函数中添加 hide() 调用来轻松解决,我们终于得到了我的问题:

我是否正确使用 ModalTrigger,因为在卸载父级时它会消失?这就是我希望 React 正常工作的方式,这意味着 react-bootstrap 中的一个错误.

或者我应该显式调用 hide() ,因为这是组件的设计方式?

以下是一段重现此内容的代码.

谢谢!

var DeleteModal = React.createClass({渲染:函数(){返回 (<ReactBootstrap.Modal onRequestHide = {this.props.onRequestHide} title = "删除这个?"><div className="modal-body">你确定吗?

<div className="modal-footer"><button onClick={this.props.onOkClick}>ok</button><button onClick={this.props.onRequestHide}>cancel</button>

</ReactBootstrap.Modal>);}});var Item = React.createClass({_onClick:函数(){//this.refs.theTrigger.hide();this.props.onClick(this.props.name);},渲染:函数(){返回 (<div><span>{this.props.name}</span><ModalTrigger modal={<DeleteModal onOkClick={this._onClick}/>} ref="theTrigger"><button>删除我!</button></ModalTrigger>

);}});var Content = React.createClass({onClick:功能(名称){this.setState({items:_.reject(this.state.items, function(item) {return item === name;})});},getInitialState:function() {返回{项目:[第一",第二",第三"]};},渲染:函数(){返回 (<div>{_.map(this.state.items, function(item, i) {返回 (<Item name={item} onClick={this.onClick} key={i}/>)}.bind(this))}

);}});React.render(, document.getElementById("mydiv"));

解决方案

事实证明这是对 React 的key"属性的滥用.我们给了映射对象整数键,所以当再次调用渲染时,给出了相同的初始键,这就是为什么 React 认为它应该重用相同的 DOM 元素.

如果我们给它 key={item} (其中 item 是一个简单的字符串),它会在我们的例子中解决它;然而,这引入了一个微妙的错误,如果有 2 个相同的字符串,React 将只显示一个.

试图通过给它 key={item + i} 来超越它会引入一个更微妙的错误,其中显示重复的项目但被大量删除,但在这种情况下,错误在 onClick 方法中,需要修改为接受某种索引.

因此我的结论是键必须是唯一的字符串,并且回调在执行任何修改时应该考虑这些键.

We encountered a strange behavior when using react-bootstrap's ModalTrigger with an array of items, in that it doesn't go away when the parent/owner item is unmounted. We suspect this has to do with React's virtual DOM and the diffing mechanism, and/or our own misuse of the ModalTrigger.

The setup is simple: a Content react component has a state that holds an array of item names. It also has an onClick(name) function that removes that name from the array via setState. In the render, it uses _.map to create a bunch of Item react components.

Each Item component displays its name and a ModalTrigger that holds a button labeled "delete me". Click on the button and it opens the modal; click "OK" in the modal and it executes the callback to the Content remove function.

When deleting the last item it works fine: the final Item component is unmounted, and with it, the ModalTrigger and its corresponding modal.

The problematic behavior we see is when deleting any item other than the last one. The item is removed but the modal stays open, whereas I would naively expect the modal to disappear since the parent ModalTrigger is gone. Not only that, but when clicking "ok" again, the next item on the list is removed, and so on until the modal happens to be associated with the last item, at which point clicking "ok" will finally hide it.

Our collective hunch is that this is caused by the overlayMixin's _overlayTarget being an anonymous element in the document, so that different ModalTriggers don't differentiate between them. Therefore, when a parent unmounts and React looks for the DOM diff, it sees the previous trigger's and says "hey, that could work".

This whole issue can easily be addressed by adding a hide() call in the Item's inner _onClick() function as is commented out in the code, and we finally arrive at my question:

Am I using ModalTrigger correctly, in that expecting it to go away when the parent is unmounting? This is kind of how I expect React to work in general, which means a bug in react-bootstrap.

Or should I be explicitly calling hide() because that's they way this component was designed?

Following is a piece of code that reproduces this.

Thanks!

var DeleteModal = React.createClass({
    render:function() {
        return (
            <ReactBootstrap.Modal onRequestHide = {this.props.onRequestHide} title = "delete this?">
                <div className="modal-body">
                    Are you sure?
                </div>
                <div className="modal-footer">
                    <button onClick={this.props.onOkClick}>ok</button>
                    <button onClick={this.props.onRequestHide}>cancel</button>
                </div>
            </ReactBootstrap.Modal>
        );
    }
});

var Item = React.createClass({
    _onClick:function() {
        //this.refs.theTrigger.hide();
        this.props.onClick(this.props.name);
    },
    render:function() {
        return (
            <div>
                <span>{this.props.name}</span>
                <ModalTrigger modal={<DeleteModal onOkClick={this._onClick}/>} ref="theTrigger">
                    <button>delete me!</button>
                </ModalTrigger>
            </div>
        );
    }
});

var Content = React.createClass({
    onClick:function(name) {
        this.setState({items:_.reject(this.state.items, function(item) {return item === name;})});
    },
    getInitialState:function() {
        return {items : ["first", "secondth", "thirdst"]};
    },
    render:function() {
        return (
            <div>
                {_.map(this.state.items, function(item, i) {
                    return (
                        <Item name={item} onClick={this.onClick} key={i}/>
                    )}.bind(this)
                )}
            </div>
        );
    }
});

React.render(<Content/>, document.getElementById("mydiv"));

解决方案

Turns out it was a misuse of React's "key" property. We gave the mapped objects integer keys, so when the render was called again, the same initial keys were given, which is why React thought it should reuse the same DOM element.

If instead we give it key={item} (where item is a simple string) it solves it in our case; however, this introduces a subtle bug whereby if there are 2 identical strings, React will display only one.

Trying to outsmart it by giving it key={item + i} introduces an even subtler bug, where duplicate items are displayed but are delete en mass, but in this case the bug is in the onClick method which would need to be modified to accept an index of some sort.

Therefore my take-away is that the keys must be a unique string, and callbacks should take these keys into consideration when performing any modifications.

这篇关于卸载父元素时,react-bootstrap ModalTrigger 不会隐藏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆