2015-03-07 5 views
4

Я хочу создать и показать обратную связь рядом с различными элементами DOM в зависимости от действий пользователя на странице. Я могу позиционировать модальный, но всякий раз, когда я пытаюсь добавить информацию, он начинает давать эти ошибки - Invariant Violation: findComponentRoot ". Мой вопрос: правильно ли это использовать библиотеку и как я могу исправить эти ошибки. Вот plunker за тот же http://plnkr.co/edit/alF7JyQAhBwcANyrQQiwКак использовать React Component с Tether.js/drop.js

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

Я имел аналогичные проблемы, и решение было создать элементы, которые должны быть прикреплены снаружи React управлением дерева.

Я также написал несколько помощников для интеграции троса с реактивом, you can see them here.

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; 
+0

Любые планы сделать это в компоненте с открытым исходным кодом? :) – geoboy

+0

@geoboy уже, хотя из этого, но у меня нет времени прямо сейчас извините –