Skip to content

Instantly share code, notes, and snippets.

@mik01aj
Last active April 21, 2017 13:02
Show Gist options
  • Save mik01aj/5b98bad5b5ba04a1b93f to your computer and use it in GitHub Desktop.
Save mik01aj/5b98bad5b5ba04a1b93f to your computer and use it in GitHub Desktop.
How to use Tether with React

Tether is a great library for positioning stuff (tooltips, modals, hints, etc) in your web app.

But, as I use React, it was pretty problematic for me, as Tether mutates the DOM and React breaks miserably when it sees mutated DOM. The solution is to have the tethered element outside the part of the DOM tree which is controlled by React (in this case, I use document.body).

That's why I created 2 helpers to use Tether with React.

The first one, TetheredElement is a plain JS helper to create a new element, attach it to some other one via Tether, and populate it with some React component.

The second one, TetherTarget is a React component and it uses TetheredElement to integrate it further with React, so that you can attach components to each other with Tether, without leaving the cozy React/JSX world and worrying about manual DOM operations. Just write:

var tetherOptions = {
    // element and target are set automatically
    attachment: 'top center',
    constraints: [
        {to: 'window', pin: true, attachment: 'together'},
    ],
};
<TetherTarget tethered={ <i>I'm tethered!</i> } tetherOptions={ tetherOptions }>
    <h1>Hello world! I'm a big box, which is the Tether target!</h1>
</TetherTarget>

Comments are welcome :)

'use strict';
var _ = require('lodash');
var React = require('react');
var Tether = require('tether-browserify/tether');
// NOTE: This is not a React component. It's a plain JS object that manages a React component.
function TetheredElement(reactComponent, tetherOptions) {
this.reactComponent = reactComponent;
this.domNode = document.createElement('div');
this.domNode.style.position = 'absolute'; // needed for Tether
document.body.appendChild(this.domNode);
this.tether = new Tether(_.merge({
element: this.domNode,
}, tetherOptions));
this.update();
}
TetheredElement.prototype.update = function () {
React.render(
this.reactComponent,
this.domNode,
() => this.tether.position()
);
};
TetheredElement.prototype.destroy = function () {
React.unmountComponentAtNode(this.domNode);
this.domNode.parentNode.removeChild(this.domNode);
this.tether.destroy();
};
module.exports = TetheredElement;
/** @jsx React.DOM */
'use strict';
var _ = require('lodash');
var React = require('react');
var TetheredElement = require('./TetheredElement');
var TetherTarget = React.createClass({
propTypes: {
tethered: React.PropTypes.node.isRequired,
tetherOptions: React.PropTypes.object.isRequired
},
getInitialState: function () {
return {tooltipVisible: false};
},
componentDidMount: function () {
var tetherOptions = _.merge({
target: this.getDOMNode(),
}, this.props.tetherOptions);
this.tethered = new TetheredElement(this.props.tethered, tetherOptions);
},
componentWillUnmount: function () {
this.tethered.destroy();
},
componentDidUpdate: function () {
this.tethered.update();
},
render: function () {
var divProps = _.omit(this.props, ['tethered', 'tetherOptions']);
return <div {... divProps }>
{ this.props.children }
</div>;
},
});
module.exports = TetherTarget;
@slorber
Copy link

slorber commented Dec 12, 2015

@silvenon not sure to understand, Tether is already a tech used to add stuff to body, ie a portal.
How can you use both together and why?

@ccnokes
Copy link

ccnokes commented Jan 19, 2016

@silvenon I'd love to see an example of how you're using react-portal with tether or drop.

@silvenon
Copy link

Hey guys, sorry I didn't see these comments, I wish Gist had notifications 😔 I set up a quick example with Drop + React Portal + Redux. It has some docs which briefly explain why React Portal is useful in this case. It's a simplified version of how I use it, I haven't tested if I made some stupid mistake somewhere, but the concept itself works.

@silvenon
Copy link

Actually, you should try react-tether by @souporserious, much cleaner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment