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