diff --git a/src/TetherComponent.jsx b/src/TetherComponent.jsx
index 0e555ae..a02c043 100644
--- a/src/TetherComponent.jsx
+++ b/src/TetherComponent.jsx
@@ -76,30 +76,35 @@ class TetherComponent extends Component {
_tether = null;
+ _elementComponent = null;
+
+ _targetComponent = null;
+
constructor(props) {
super(props);
- const elementComponent = Children.toArray(props.children)[1];
- if (elementComponent) {
- this._createContainer();
- }
+ this.updateChildrenComponents(this.props);
}
- componentWillUpdate({ children }) {
- const elementComponent = Children.toArray(children)[1];
+ updateChildrenComponents(props) {
+ const childArray = Children.toArray(props.children);
+ this._targetComponent = childArray[0];
+ this._elementComponent = childArray[1];
- if (elementComponent) {
+ if (this._targetComponent && this._elementComponent) {
this._createContainer();
}
}
+ componentWillUpdate(nextProps) {
+ this.updateChildrenComponents(nextProps);
+ }
+
componentDidMount() {
- this._targetNode = ReactDOM.findDOMNode(this);
this._update();
}
componentDidUpdate() {
- this._targetNode = ReactDOM.findDOMNode(this);
this._update();
}
@@ -170,13 +175,16 @@ class TetherComponent extends Component {
this._elementParentNode = null;
this._tether = null;
+ this._targetNode = null;
+ this._targetComponent = null;
+ this._elementComponent = null;
}
_createContainer() {
- const { renderElementTag } = this.props;
-
// Create element node container if it hasn't been yet
if (!this._elementParentNode) {
+ const { renderElementTag } = this.props;
+
// Create a node that we can stick our content Component in
this._elementParentNode = document.createElement(renderElementTag);
@@ -186,15 +194,16 @@ class TetherComponent extends Component {
}
_update() {
- const { children } = this.props;
- const elementComponent = Children.toArray(children)[1];
-
// If no element component provided, bail out
- if (!elementComponent) {
- // Destroy Tether element if it has been created
- if (this._tether) {
- this._destroy();
- }
+ let shouldDestroy = !this._elementComponent || !this._targetComponent;
+ if (!shouldDestroy) {
+ this._targetNode = ReactDOM.findDOMNode(this);
+ shouldDestroy = !this._targetNode;
+ }
+
+ if (shouldDestroy) {
+ // Destroy Tether element, or parent node, if those has been created
+ this._destroy();
return;
}
@@ -204,7 +213,7 @@ class TetherComponent extends Component {
// Render element component into the DOM
ReactDOM.unstable_renderSubtreeIntoContainer(
this,
- elementComponent,
+ this._elementComponent,
this._elementParentNode,
() => {
// If we're not destroyed, update Tether once the subtree has finished rendering
@@ -232,17 +241,22 @@ class TetherComponent extends Component {
...options,
};
- if (id) {
- this._elementParentNode.id = id;
+ const idStr = id || '';
+ if (this._elementParentNode.id !== idStr) {
+ this._elementParentNode.id = idStr;
}
- if (className) {
- this._elementParentNode.className = className;
+ const classStr = className || '';
+ if (this._elementParentNode.className !== classStr) {
+ this._elementParentNode.className = classStr;
}
if (style) {
+ const elementStyle = this._elementParentNode.style;
Object.keys(style).forEach(key => {
- this._elementParentNode.style[key] = style[key];
+ if (elementStyle[key] !== style[key]) {
+ elementStyle[key] = style[key];
+ }
});
}
@@ -257,16 +271,17 @@ class TetherComponent extends Component {
}
render() {
- const { children } = this.props;
- const elementComponent = Children.toArray(children)[1];
+ if (!this._targetComponent) {
+ return null;
+ }
- if (!hasCreatePortal || !elementComponent) {
- return Children.toArray(children)[0];
+ if (!hasCreatePortal || !this._elementComponent) {
+ return this._targetComponent;
}
return [
- Children.toArray(children)[0],
- ReactDOM.createPortal(elementComponent, this._elementParentNode),
+ this._targetComponent,
+ ReactDOM.createPortal(this._elementComponent, this._elementParentNode),
];
}
}
diff --git a/tests/unit/component.test.js b/tests/unit/component.test.js
index 9ca879d..eae9e28 100644
--- a/tests/unit/component.test.js
+++ b/tests/unit/component.test.js
@@ -87,15 +87,36 @@ describe('TetherComponent', () => {
expect(document.querySelector('.tether-element')).toBeFalsy();
});
- it('should destroy the tether element if the second child is unmounted', () => {
+ it('should not create a tether element if there is no target', () => {
+ wrapper = mount(
+
+ {null}
+
+
+ );
+ expect(document.querySelector('.tether-element')).toBeFalsy();
+ });
+
+ it('should not create a tether element if there is no dom node for target', () => {
+ const FalsyComponent = () => null;
+ wrapper = mount(
+
+
+
+
+ );
+ expect(document.querySelector('.tether-element')).toBeFalsy();
+ });
+
+ it('should destroy the tether element if the first/second child is unmounted', () => {
class ToggleComponent extends React.Component {
- state = { on: true };
+ state = { firstOn: true, secondOn: true };
render() {
return (
-
- {this.state.on && }
+ {this.state.firstOn && }
+ {this.state.secondOn && }
);
}
@@ -106,11 +127,23 @@ describe('TetherComponent', () => {
expect(document.querySelector('.tether-element')).toBeTruthy();
expect(document.querySelector('.tether-element #child2')).toBeTruthy();
- wrapper.setState({ on: false });
+ wrapper.setState({ secondOn: false });
expect(wrapper.find('#child1').exists()).toBeTruthy();
expect(document.querySelector('.tether-element')).toBeFalsy();
expect(document.querySelector('.tether-element #child2')).toBeFalsy();
+
+ wrapper.setState({ firstOn: false, secondOn: true });
+
+ expect(wrapper.find('#child1').exists()).toBeFalsy();
+ expect(document.querySelector('.tether-element')).toBeFalsy();
+ expect(document.querySelector('.tether-element #child2')).toBeFalsy();
+
+ wrapper.setState({ firstOn: false, secondOn: false });
+
+ expect(wrapper.find('#child1').exists()).toBeFalsy();
+ expect(document.querySelector('.tether-element')).toBeFalsy();
+ expect(document.querySelector('.tether-element #child2')).toBeFalsy();
});
it('allows changing the tether element tag', () => {