如何在Tether.js / drop.js中使用React Component

时间:2015-03-07 22:49:17

标签: javascript reactjs hubspot tether

我想根据页面上的用户操作在不同的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'));

2 个答案:

答案 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;