3

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"));
4

1 回答 1

0

事实证明这是对 React 的“关键”属性的滥用。我们为映射对象提供整数键,因此当再次调用渲染时,会给出相同的初始键,这就是 React 认为它应该重用相同 DOM 元素的原因。

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

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

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

于 2015-02-26T12:47:26.410 回答