Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
An example dropdown using Tether Drop, React Portal and Redux.

Tether Drop + React Portal + Redux

This is an example of using Tether Drop with React Portal (and Redux for managing the state). I was asked if using React Portal is redundant, as both libraries pull the content into the <body>. Using only Drop would cause an invariant violation in React becuase of the DOM mutation, so I'm using React Portal to first bring it outside without React complaining (I don't know how React Portal does it, I haven't checked out the source, but it works). Once it's out of React's supervision, I can apply Drop to it.

  • Dropdown.jsx is the actual dropdown component
  • App.jsx is just an demo that uses it

This is my lazy way out of this limitation using an existing library that has much more features than you need, but chances are that you're going to need a library like React Portal anyway for stuff like modals.

Notes

  • should work for any Tether library (and Tether itself)
  • this setup assumes
    • Babel or a similar transpiler
    • webpack or a similar module bundler
  • I'm using Redux instead of letting Drop handling the toggling because I want to have this state in the store
import React, { PropTypes } from 'react';
import Portal from 'react-portal';
import Drop from 'tether-drop';
import 'tether-drop/dist/css/drop-theme-hubspot-popovers.css';
class Dropdown extends React.Component {
static propTypes = {
label: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
};
static defaultProps = {
open: false,
};
componentDidMount() {
this.drop = new Drop({
target: this.target,
content: this.content,
position: 'bottom center',
classes: 'drop-theme-hubspot-popovers',
openOn: undefined, // we'll manually open and close the dropdown
});
if (this.props.open) {
this.drop.open();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.open) {
this.drop.open();
} else {
this.drop.close();
}
}
componentDidUpdate() {
// update the position if content has changed
this.drop.position();
}
componentWillUnmount() {
this.drop.destroy();
}
_refTarget = (ref) => {
this.target = ref;
};
_refContent = (ref) => {
this.content = ref;
};
render() {
return (
<div ref={this._refTarget}>
<button
type="button"
onClick={this.props.onClick}
>
{this.props.label}
</button>
<Portal isOpened={true}>
<div ref={this._refContent}>
{'I'm the drop content!'}
</div>
</Portal>
</div>
);
}
}
export default Dropdown;
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import Dropdown from './Dropdown';
function reducer(state = { open: false }, action) {
switch (action.type) {
case 'TOGGLE_DROPDOWN':
return {
...state,
open: !state.open
};
default:
return state;
}
}
const store = createStore(reducer);
const App = (props) => (
<div>
<Dropdown
label="Click me"
open={store.getState().open}
onClick={() => {
store.dispatch({ action: 'TOGGLE_DROPDOWN' });
}}
/>
</div>
);
function render() {
ReactDOM.render(<App />, document.getElementById('root'));
}
store.subscribe(render);
render();
@RyanBertrand

This comment has been minimized.

Copy link

commented Oct 12, 2016

Thanks for sharing this. Were you able to keep on-click events working? It seems when the content is added to Drop, all the event handlers are lost.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.