我想根据页面上的用户操作在不同的DOM元素旁边创建并显示反馈模式。我能够定位模态,但每当我尝试添加信息时,它就会开始给出这些错误 - 不变违规:findComponentRoot“。 我的问题是,这是使用库的正确方法,我该如何解决这些错误。 这是相同的plunker http://plnkr.co/edit/alF7JyQAhBwcANyrQQiw
var Feedback = React.createClass({
clickHandler: function(){
console.log("form is submitted");
},
componentDidMount: function(){
var el = this.getDOMNode();
var drop = new Drop({
target: document.querySelector('#test'),
classes: 'drop-theme-arrows-bounce drop-hero',
content: el,
openOn: "click",
tetherOptions: {
attachment: 'bottom right',
targetOffset: "0 10px"
}
});
},
render: function(){
return (
<div className="drop-theme-hubspot-popovers">
<form>
<div className="form-group">
<label>Feedback</label>
<input type="text" className="form-control"
placeholder="Enter email"
onChange={this.changeHandler}/>
<a href="#" className="btn btn-default" onClick={this.clickHandler}>Submit</a>
</div>
</form>
</div>
);
}
});
var Demo = React.createClass({
getInitialState: function(){
return {feedback: null};
},
componentDidMount: function(){
var FeedbackElement = React.createFactory(Feedback);
var feedback = <FeedbackElement/>;
//React.render(feedback, document.querySelector('#targetName'));
this.setState({feedback:feedback});
},
render: function(){
return (
<div className="container">
<div className="page-header">
<h1>Hello</h1>
</div>
<div className="row">
<div className="col-sm-12">
<div className="col-lg-5">
<a name="test" id="test" className="btn btn-default" onClick={this.clickHandler}> Click</a>
</div>
</div>
</div>
{this.state.feedback}
</div>
);
}
});
React.render(Demo(), document.getElementById('app'));
答案 0 :(得分:2)
我遇到了类似的问题,解决方法是创建要在React控制树之外附加的元素。
我还写了一些帮助将Tether与React,you can see them here进行整合。
答案 1 :(得分:2)
有关信息,我们使用Tether Tooltip。它只是DropJS的一个非常简单的包装器(它只是添加了一些默认和CSS类),所以希望你能够在DropJS上使用相同类型的代码。
我们已经创建了一个包装器组件WithTooltip。你可以这样简单地使用它:
render: function () {
return (
<WithTooltip content={this.renderTooltipContent()} position="bottom left">
{this.renderContent()}
</WithTooltip>
);
}
请注意,工具提示(或删除)内容既可以是简单文本,也可以是React组件。该行为与"portal"
非常相似您也可以在工具提示内容中使用React context,但从0.14起,它将需要使用新方法renderSubtreeIntoContainer
这是我们目前使用的原始完整WithTooltip代码。
'use strict';
var React = require("react");
var _ = require("lodash");
var $ = require("jquery");
var TetherTooltip = require("tether-tooltip");
var WithLongHoverBehavior = require("common/withLongHoverBehavior");
var AppMediaqueries = require("appMediaqueries");
// See https://github.com/facebook/react/issues/4081
// See https://github.com/facebook/react/pull/4184
// See https://github.com/facebook/react/issues/4301
//var renderSubtreeIntoContainer = require("react-dom").unstable_renderSubtreeIntoContainer;
var ValidTooltipPositions = [
'top left',
'left top',
'left middle',
'left bottom',
'bottom left',
'bottom center',
'bottom right',
'right bottom',
'right middle',
'right top',
'top right',
'top center'
];
var TooltipConstraints = [
{
to: 'window',
attachment: 'together',
// Can be important because tether can switch from top to bottom, or left to right,
// but it does not handle correctly bottom-left to bottom-right for exemple
// Using pin will at least make the tooltip stay on the screen without overflow
// (but there's a CSS bug that makes the tooltip arrow hidden by the content I think)
pin: true
}
];
/**
* A wrapper to set around components that must have a tooltip
* The tooltip knows how to reposition itself according to constraints on scroll/resize...
* See http://github.hubspot.com/tooltip/
*/
var WithTooltip = React.createClass({
propTypes: {
// The children on which the tooltip must be displayed on hover
children: React.PropTypes.node.isRequired,
// The prefered position (by default it will try to constrain the tooltip into window boundaries
position: React.PropTypes.oneOf(ValidTooltipPositions),
// The tooltip content (can be an inlined HTML string or simple text)
// If not defined, the tooltip will be disabled
content: React.PropTypes.node,
// Permits to disable the tooltip
disabled: React.PropTypes.bool,
// Wether this tooltip can be hovered or not (useful if the tooltip contains buttons)
hoverable: React.PropTypes.bool
},
isDisabled: function() {
if ( this.props.disabled ) {
return true;
}
else if ( !this.props.content ) {
return true;
}
else {
return false;
}
},
// TODO can probably be optimized?
resetTooltipForCurrentProps: function() {
// The timeout is required because otherwise TetherTooltip messes up with animations entering (ReactCSSTransitionGroup)
// TODO find why! is there a better solution?
setTimeout(function() {
if (this.isMounted()) {
this.destroyTooltip();
// Disable tooltips for mobile, as there's no mouse it does not make sense
// In addition we have encountered weird behaviors in iPhone/iOS that triggers "mouseover" events on touch,
// even after calling preventDefault on the touchstart/end events :(
if ( AppMediaqueries.isMobile() ) {
this.destroyTooltip();
return;
}
if ( !this.isDisabled() ) {
var target = React.findDOMNode(this);
if ( $(target).width() === 0 && $(target).height() === 0 ) {
console.warn("WithTooltip: you are setting a tooltip on an element with 0 width/height. This is probably unwanted behavior",target);
}
this.tetherTooltip = new TetherTooltip({
target: target,
position: this.props.position || 'bottom left',
content: " ", // Disable as we manage the content ourselves
// See https://github.com/HubSpot/tooltip/issues/5#issuecomment-33735589
tetherOptions: {
constraints: TooltipConstraints
}
});
if ( this.props.hoverable ) {
$(this.getTetherTooltipNode()).addClass("tooltip-hoverable");
}
// We mount the tooltip content ourselves because we want to be able to mount React content as tooltip
var tooltipContentNode = $(this.getTetherTooltipNode()).find(".tooltip-content")[0];
if ( React.isValidElement(this.props.content) ) {
//renderSubtreeIntoContainer(this, this.props.content, tooltipContentNode);
React.render(this.props.content, tooltipContentNode);
}
else {
tooltipContentNode.innerHTML = this.props.content;
}
}
}
}.bind(this),0);
},
componentDidMount: function() {
this.resetTooltipForCurrentProps();
},
componentDidUpdate: function(previousProps) {
var positionHasChanged = (this.props.position !== previousProps.position);
var contentHasChanged = (this.props.content !== previousProps.content);
var disabledHasChanged = (this.props.disabled !== previousProps.disabled);
var childrenHasChanged = (this.props.children !== previousProps.children);
var hasChanged = positionHasChanged || disabledHasChanged || contentHasChanged || childrenHasChanged;
if ( hasChanged ) {
this.resetTooltipForCurrentProps();
}
},
componentWillUnmount: function() {
this.destroyTooltip();
},
destroyTooltip: function() {
if ( this.tetherTooltip ) {
this.tetherTooltip.destroy();
delete this.tetherTooltip;
}
},
getTooltipTarget: function() {
if (typeof this.props.children === 'string') {
return <span>{this.props.children}</span>;
} else {
return React.Children.only(this.props.children);
}
},
// It may return nothing if the tooltip is already removed from DOM
getTetherTooltipNode: function() {
return this.tetherTooltip && this.tetherTooltip.drop && this.tetherTooltip.drop.drop;
},
onLongHover: function() {
$(this.getTetherTooltipNode()).addClass("long-hover");
},
onHoverEnd: function() {
$(this.getTetherTooltipNode()).removeClass("long-hover");
},
render: function() {
return (
<WithLongHoverBehavior longHoverDelay={2500} onLongHover={this.onLongHover} onHoverEnd={this.onHoverEnd}>
{this.getTooltipTarget()}
</WithLongHoverBehavior>
);
}
});
module.exports = WithTooltip;