Skip to content

Instantly share code, notes, and snippets.

@borm
Forked from silvenon/0.Readme.md
Created February 24, 2018 10:34
Show Gist options
  • Save borm/db9643072680d7e629d9f6f45f96bcca to your computer and use it in GitHub Desktop.
Save borm/db9643072680d7e629d9f6f45f96bcca to your computer and use it in GitHub Desktop.
An example dropdown using Tether Drop, React Portal and 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();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment