diff --git a/README.md b/README.md
index 3347832..2f75335 100644
--- a/README.md
+++ b/README.md
@@ -43,23 +43,27 @@ class SimpleDemo extends React.Component {
attachment: 'together',
},
]}
- >
- {/* First child: This is what the item will be tethered to */}
-
- {/* Second child: If present, this item will be tethered to the the first child */}
- {isOpen && (
-
-
Tethered Content
-
A paragraph to accompany the title.
-
+ /* renderTarget: This is what the item will be tethered to, make sure to attach the ref */
+ renderTarget={ref => (
+
)}
-
+ /* renderElement: If present, this item will be tethered to the the component returned by renderTarget */
+ renderElement={ref =>
+ isOpen && (
+
+
Tethered Content
+
A paragraph to accompany the title.
+
+ )
+ }
+ />
);
}
}
@@ -67,9 +71,13 @@ class SimpleDemo extends React.Component {
## Props
-#### `children`: PropTypes.node.isRequired (2 Max)
+#### `renderTarget`: PropTypes.func
-The first child is used as the Tether's `target` and the second child (which is optional) is used as Tether's `element` that will be moved.
+This is a [render prop](https://reactjs.org/docs/render-props.html), the component returned from this function will be Tether's `target`. One argument, ref, is passed into this function. This is a ref that must be attached to the highest possible DOM node in the tree. If this is not done the element will not render.
+
+#### `renderElement`: PropTypes.func
+
+This is a [render prop](https://reactjs.org/docs/render-props.html), the component returned from this function will be Tether's `element`, that will be moved. If no component is returned, the target will still render, but with no element tethered. One argument, ref, is passed into this function. This is a ref that must be attached to the highest possible DOM node in the tree. If this is not done the element will not render.
#### `renderElementTag`: PropTypes.string
@@ -85,6 +93,10 @@ Tether requires this element to be `position: static;`, otherwise it will defaul
Any valid [Tether options](http://tether.io/#options).
+#### `children`:
+
+Previous versions of react-tether used children to render the target and component, using children will now throw an error. Please use renderTarget and renderElement instead
+
## Imperative API
The following methods are exposed on the component instance:
diff --git a/bin/test b/bin/test
index edaa4be..6b025ac 100755
--- a/bin/test
+++ b/bin/test
@@ -8,11 +8,6 @@ npm run build
npm run lint
npm run typescript
-# Unit test React 15 and collect coverage
-npm run react:15
-npm run unit -- --coverage
-mv coverage/coverage-final.json coverage/coverage-react15.json
-
# Unit test React 16 and collect coverage
npm run react:16
npm run unit -- --coverage
@@ -22,7 +17,6 @@ mv coverage/coverage-final.json coverage/coverage-react16.json
mkdir -p .nyc_output
npx istanbul-merge \
--out .nyc_output/coverage-final.json \
- coverage/coverage-react15.json \
coverage/coverage-react16.json
rm -rf coverage
diff --git a/example/components/demo.js b/example/components/demo.js
index c024de8..0e393d7 100644
--- a/example/components/demo.js
+++ b/example/components/demo.js
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { RefObject } from 'react';
import styled from 'styled-components';
import Draggable from 'react-draggable';
import chroma from 'chroma-js';
@@ -30,16 +30,21 @@ type DraggableTargetProps = {
id: string,
width: number,
};
-const DraggableTarget = ({
- color,
- height,
- id,
- width,
- ...props
-}: DraggableTargetProps) => (
-
-
-
+const DraggableTarget = React.forwardRef(
+ (
+ { color, height, id, width, ...props }: DraggableTargetProps,
+ ref: RefObject
+ ) => (
+
+
+
+ )
);
const Text = styled.p`
@@ -152,25 +157,29 @@ export default class Demo extends React.Component {
attachment: 'together',
},
]}
- >
-
- this.tether.getTetherInstance() && this.tether.position()
- }
- defaultPosition={{ x: 25, y: 125 }}
- />
- {this.state.on && (
-
- Drag the box around
- I'll stay within the outline
-
+ renderTarget={ref => (
+
+ this.tether.getTetherInstance() && this.tether.position()
+ }
+ defaultPosition={{ x: 25, y: 125 }}
+ />
)}
-
+ renderElement={ref =>
+ this.state.on && (
+
+ Drag the box around
+ I'll stay within the outline
+
+ )
+ }
+ />
)}
diff --git a/example/components/page-title.js b/example/components/page-title.js
index 71af267..c2c7dfb 100644
--- a/example/components/page-title.js
+++ b/example/components/page-title.js
@@ -77,16 +77,20 @@ class PageTitle extends React.Component {
attachment: 'together',
},
]}
- >
-
-
- {children}
-
-
+ renderTarget={ref => (
+
+ )}
+ renderElement={ref => (
+
+ {children}
+
+ )}
+ />
);
}
diff --git a/example/index.js b/example/index.js
index 6e57bd1..67ec7a9 100644
--- a/example/index.js
+++ b/example/index.js
@@ -61,9 +61,14 @@ class App extends Component {
import TetherComponent from 'react-tether';
const TetheredThing = () => (
-
-
The target component
-
The tethered component
+ (
+
The target component
+ )}
+ renderElement={ref => (
+
The tethered component
+ )}
)
`}
diff --git a/package.json b/package.json
index aa383b0..d6b3bb8 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
"test": "npm run lint && npm run typescript && npm run unit",
"tdd": "npm run unit -- --watch",
"react:16": "enzyme-adapter-react-install 16",
- "react:15": "enzyme-adapter-react-install 15.5",
"danger": "danger ci",
"typescript": "tsc -p tsconfig.json",
"lint": "xo"
@@ -45,8 +44,8 @@
"tether": "^1.4.5"
},
"peerDependencies": {
- "react": "^0.14.0 || ^15.0.0 || ^16.0.0",
- "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0"
+ "react": "^16.3.0",
+ "react-dom": "^16.3.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
diff --git a/src/TetherComponent.jsx b/src/TetherComponent.jsx
index d651b28..1406a0a 100644
--- a/src/TetherComponent.jsx
+++ b/src/TetherComponent.jsx
@@ -1,4 +1,4 @@
-import { Component, Children } from 'react';
+import React, { Component, Children, isValidElement } from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import Tether from 'tether';
@@ -9,8 +9,6 @@ if (!Tether) {
);
}
-const hasCreatePortal = ReactDOM.createPortal !== undefined;
-
const renderElementToPropTypes = [
PropTypes.string,
PropTypes.shape({
@@ -20,14 +18,11 @@ const renderElementToPropTypes = [
const childrenPropType = ({ children }, propName, componentName) => {
const childCount = Children.count(children);
- if (childCount <= 0) {
+ if (childCount > 0) {
return new Error(
- `${componentName} expects at least one child to use as the target element.`
+ `${componentName} no longer uses children to render components. Please use renderTarget and renderElement instead.`
);
}
- if (childCount > 2) {
- return new Error(`Only a max of two children allowed in ${componentName}.`);
- }
};
const attachmentPositions = [
@@ -62,6 +57,8 @@ class TetherComponent extends Component {
style: PropTypes.object,
onUpdate: PropTypes.func,
onRepositioned: PropTypes.func,
+ renderTarget: PropTypes.func,
+ renderElement: PropTypes.func,
children: childrenPropType,
};
@@ -70,42 +67,35 @@ class TetherComponent extends Component {
renderElementTo: null,
};
- _targetNode = null;
-
- _elementParentNode = null;
+ // The DOM node of the target, obtained using ref in the render prop
+ _targetNode = React.createRef();
- _tether = null;
+ // The DOM node of the element, obtained using ref in the render prop
+ _elementNode = React.createRef();
- _elementComponent = null;
-
- _targetComponent = null;
+ _elementParentNode = null;
- constructor(props) {
- super(props);
+ _tetherInstance = null;
- this.updateChildrenComponents(this.props);
+ componentDidMount() {
+ this._createContainer();
+ // The container is created after mounting
+ // so we need to force an update to
+ // enable tether
+ // Cannot move _createContainer into the constructor
+ // because of is a side effect: https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects
+ this.forceUpdate();
}
- updateChildrenComponents(props) {
- const childArray = Children.toArray(props.children);
- this._targetComponent = childArray[0];
- this._elementComponent = childArray[1];
-
- if (this._targetComponent && this._elementComponent) {
+ componentDidUpdate(prevProps) {
+ // If the container related props have changed, then update the container
+ if (
+ prevProps.renderElementTag !== this.props.renderElementTag ||
+ prevProps.renderElementTo !== this.props.renderElementTo
+ ) {
this._createContainer();
}
- }
-
- // eslint-disable-next-line react/no-deprecated
- componentWillUpdate(nextProps) {
- this.updateChildrenComponents(nextProps);
- }
- componentDidMount() {
- this._update();
- }
-
- componentDidUpdate() {
this._update();
}
@@ -114,31 +104,75 @@ class TetherComponent extends Component {
}
getTetherInstance() {
- return this._tether;
+ return this._tetherInstance;
}
disable() {
- this._tether.disable();
+ this._tetherInstance.disable();
}
enable() {
- this._tether.enable();
+ this._tetherInstance.enable();
}
on(event, handler, ctx) {
- this._tether.on(event, handler, ctx);
+ this._tetherInstance.on(event, handler, ctx);
}
once(event, handler, ctx) {
- this._tether.once(event, handler, ctx);
+ this._tetherInstance.once(event, handler, ctx);
}
off(event, handler) {
- this._tether.off(event, handler);
+ this._tetherInstance.off(event, handler);
}
position() {
- this._tether.position();
+ this._tetherInstance.position();
+ }
+
+ _runRenders() {
+ // To obtain the components, we run the render functions and pass in the ref
+ // Later, when the component is mounted, the ref functions will be called
+ // and trigger a tether update
+ let targetComponent =
+ typeof this.props.renderTarget === 'function'
+ ? this.props.renderTarget(this._targetNode)
+ : null;
+ let elementComponent =
+ typeof this.props.renderElement === 'function'
+ ? this.props.renderElement(this._elementNode)
+ : null;
+
+ // Check if what has been returned is a valid react element
+ if (!isValidElement(targetComponent)) {
+ targetComponent = null;
+ }
+ if (!isValidElement(elementComponent)) {
+ elementComponent = null;
+ }
+
+ return {
+ targetComponent,
+ elementComponent,
+ };
+ }
+
+ _createTetherInstance(tetherOptions) {
+ if (this._tetherInstance) {
+ this._destroy();
+ }
+
+ this._tetherInstance = new Tether(tetherOptions);
+ this._registerEventListeners();
+ }
+
+ _destroyTetherInstance() {
+ if (this._tetherInstance) {
+ this._tetherInstance.destroy();
+
+ this._tetherInstance = null;
+ }
}
_registerEventListeners() {
@@ -162,67 +196,43 @@ class TetherComponent extends Component {
}
_destroy() {
- if (this._elementParentNode) {
- if (!hasCreatePortal) {
- ReactDOM.unmountComponentAtNode(this._elementParentNode);
- }
- this._elementParentNode.parentNode.removeChild(this._elementParentNode);
- }
-
- if (this._tether) {
- this._tether.destroy();
- }
-
- this._elementParentNode = null;
- this._tether = null;
- this._targetNode = null;
- this._targetComponent = null;
- this._elementComponent = null;
+ this._destroyTetherInstance();
+ this._removeContainer();
}
_createContainer() {
// Create element node container if it hasn't been yet
- if (!this._elementParentNode) {
- const { renderElementTag } = this.props;
+ this._removeContainer();
- // Create a node that we can stick our content Component in
- this._elementParentNode = document.createElement(renderElementTag);
+ const { renderElementTag } = this.props;
- // Append node to the render node
- this._renderNode.appendChild(this._elementParentNode);
+ // Create a node that we can stick our content Component in
+ this._elementParentNode = document.createElement(renderElementTag);
+ }
+
+ _addContainerToDOM() {
+ // Append node to the render node
+ this._renderNode.appendChild(this._elementParentNode);
+ }
+
+ _removeContainer() {
+ if (this._elementParentNode && this._elementParentNode.parentNode) {
+ this._elementParentNode.parentNode.removeChild(this._elementParentNode);
}
}
_update() {
// If no element component provided, bail out
- let shouldDestroy = !this._elementComponent || !this._targetComponent;
- if (!shouldDestroy) {
- this._targetNode = ReactDOM.findDOMNode(this);
- shouldDestroy = !this._targetNode;
- }
+ const shouldDestroy =
+ !this._elementNode.current || !this._targetNode.current;
if (shouldDestroy) {
- // Destroy Tether element, or parent node, if those has been created
+ // Destroy Tether element if it has been created
this._destroy();
return;
}
- if (hasCreatePortal) {
- this._updateTether();
- } else {
- // Render element component into the DOM
- ReactDOM.unstable_renderSubtreeIntoContainer(
- this,
- this._elementComponent,
- this._elementParentNode,
- () => {
- // If we're not destroyed, update Tether once the subtree has finished rendering
- if (this._elementParentNode) {
- this._updateTether();
- }
- }
- );
- }
+ this._updateTether();
}
_updateTether() {
@@ -233,10 +243,12 @@ class TetherComponent extends Component {
id,
className,
style,
+ renderTarget,
+ renderElement,
...options
} = this.props;
const tetherOptions = {
- target: this._targetNode,
+ target: this._targetNode.current,
element: this._elementParentNode,
...options,
};
@@ -260,29 +272,31 @@ class TetherComponent extends Component {
});
}
- if (this._tether) {
- this._tether.setOptions(tetherOptions);
+ this._addContainerToDOM();
+
+ if (this._tetherInstance) {
+ this._tetherInstance.setOptions(tetherOptions);
} else {
- this._tether = new Tether(tetherOptions);
- this._registerEventListeners();
+ this._createTetherInstance(tetherOptions);
}
- this._tether.position();
+ this._tetherInstance.position();
}
render() {
- if (!this._targetComponent) {
- return null;
- }
+ const { targetComponent, elementComponent } = this._runRenders();
- if (!hasCreatePortal || !this._elementComponent) {
- return this._targetComponent;
+ if (!targetComponent || !this._elementParentNode) {
+ return null;
}
- return [
- this._targetComponent,
- ReactDOM.createPortal(this._elementComponent, this._elementParentNode),
- ];
+ return (
+
+ {targetComponent}
+ {elementComponent &&
+ ReactDOM.createPortal(elementComponent, this._elementParentNode)}
+
+ );
}
}
diff --git a/src/react-tether.d.ts b/src/react-tether.d.ts
index 3f88eb2..a588b0c 100644
--- a/src/react-tether.d.ts
+++ b/src/react-tether.d.ts
@@ -37,11 +37,13 @@ declare namespace ReactTether {
attachment: TetherAttachment;
targetAttachment: TetherAttachment;
};
+ type RenderProp = (ref: React.RefObject) => React.ReactNode;
interface TetherComponentProps
extends React.Props,
Tether.ITetherOptions {
- children: React.ReactNode;
+ renderTarget?: RenderProp;
+ renderElement?: RenderProp;
renderElementTag?: string;
renderElementTo?: Element | string;
className?: string;
diff --git a/tests/unit/component.test.js b/tests/unit/component.test.js
index fdd9acd..56c3d17 100644
--- a/tests/unit/component.test.js
+++ b/tests/unit/component.test.js
@@ -1,10 +1,7 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import { mount } from 'enzyme';
import TetherComponent from '../../src/react-tether';
-const hasCreatePortal = ReactDOM.createPortal !== undefined;
-
describe('TetherComponent', () => {
let wrapper;
@@ -15,44 +12,35 @@ describe('TetherComponent', () => {
}
});
- it('should render the first child', () => {
+ it('should render the target', () => {
+ wrapper = mount(
+ }
+ renderElement={ref => }
+ />
+ );
+ expect(wrapper.find('#target').exists()).toBeTruthy();
+ });
+
+ it('should render the element', () => {
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
- expect(wrapper.find('#child1').exists()).toBeTruthy();
- });
-
- if (hasCreatePortal) {
- it('should render the second child', () => {
- wrapper = mount(
-
-
-
-
- );
- expect(wrapper.find('#child2').exists()).toBeTruthy();
- });
- } else {
- it('should not render the second child', () => {
- wrapper = mount(
-
-
-
-
- );
- expect(wrapper.find('#child2').exists()).toBeFalsy();
- });
- }
+ expect(wrapper.find('#element').exists()).toBeTruthy();
+ });
it('should create a tether element', () => {
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
const tetherElement = document.querySelector('.tether-element');
expect(tetherElement).toBeTruthy();
@@ -60,50 +48,54 @@ describe('TetherComponent', () => {
it('should render the second child in the tether element', () => {
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
- const child2 = document.querySelector('.tether-element #child2');
- expect(child2).toBeTruthy();
+ const element = document.querySelector('.tether-element #element');
+ expect(element).toBeTruthy();
});
- it('should render a single child', () => {
+ it('should render a just a target', () => {
wrapper = mount(
-
-
-
+ }
+ />
);
- expect(wrapper.find('#child1').exists()).toBeTruthy();
+ expect(wrapper.find('#target').exists()).toBeTruthy();
});
- it('should not create a tether element if there is a single child', () => {
+ it('should not create a tether element if there is no renderElement', () => {
wrapper = mount(
-
-
-
+ }
+ />
);
expect(document.querySelector('.tether-element')).toBeFalsy();
});
- it('should not create a tether element if there is no target', () => {
+ it('should not create a tether element if there is no renderTarget', () => {
wrapper = mount(
-
- {null}
-
-
+ }
+ />
);
expect(document.querySelector('.tether-element')).toBeFalsy();
});
- it('should not create a tether element if there is no dom node for target', () => {
+ it('should not create a tether element if ref is not bound to a dom node', () => {
const FalsyComponent = () => null;
wrapper = mount(
-
-
-
-
+ }
+ renderElement={() => }
+ />
);
expect(document.querySelector('.tether-element')).toBeFalsy();
});
@@ -114,48 +106,75 @@ describe('TetherComponent', () => {
render() {
return (
-
- {this.state.firstOn && }
- {this.state.secondOn && }
-
+
+ this.state.firstOn &&
+ }
+ renderElement={ref =>
+ this.state.secondOn &&
+ }
+ />
);
}
}
wrapper = mount();
- expect(wrapper.find('#child1').exists()).toBeTruthy();
- expect(document.querySelector('.tether-element')).toBeTruthy();
- expect(document.querySelector('.tether-element #child2')).toBeTruthy();
+ expect(wrapper.find('#target').exists()).toBeTruthy();
+ expect(document.querySelector('.tether-element #element')).toBeTruthy();
wrapper.setState({ secondOn: false });
- expect(wrapper.find('#child1').exists()).toBeTruthy();
- expect(document.querySelector('.tether-element')).toBeFalsy();
- expect(document.querySelector('.tether-element #child2')).toBeFalsy();
+ expect(wrapper.find('#target').exists()).toBeTruthy();
+ expect(document.querySelector('.tether-element #element')).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();
+ expect(wrapper.find('#target').exists()).toBeFalsy();
+ expect(document.querySelector('.tether-element #element')).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();
+ expect(wrapper.find('#target').exists()).toBeFalsy();
+ expect(document.querySelector('.tether-element #element')).toBeFalsy();
});
it('allows changing the tether element tag', () => {
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
expect(document.querySelector('.tether-element').nodeName).toBe('ASIDE');
});
+ it('allows changing the tether element tag on the fly', () => {
+ class DifferentTagsComponent extends React.Component {
+ state = { isAside: false };
+
+ render() {
+ return (
+ }
+ renderElement={ref => }
+ />
+ );
+ }
+ }
+ wrapper = mount();
+
+ expect(document.querySelector('.tether-element').nodeName).toBe('DIV');
+
+ wrapper.setState({ isAside: true });
+
+ expect(document.querySelector('.tether-element').nodeName).toBe('ASIDE');
+ });
+
it('allows changing the tether element tag', () => {
const container = document.createElement('div');
container.setAttribute('id', 'test-container');
@@ -164,10 +183,12 @@ describe('TetherComponent', () => {
document.body.appendChild(container);
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
expect(document.querySelector('#test-container')).toBeTruthy();
@@ -176,6 +197,49 @@ describe('TetherComponent', () => {
).toBeTruthy();
});
+ it('allows changing the tether element tag on the fly', () => {
+ const container = document.createElement('div');
+ container.setAttribute('id', 'test-container');
+ // Tether requires the container element to have position static
+ container.style.position = 'static';
+ document.body.appendChild(container);
+
+ const container2 = document.createElement('div');
+ container2.setAttribute('id', 'test-container2');
+ container2.style.position = 'static';
+ document.body.appendChild(container2);
+
+ class DifferentRenderElementToComponent extends React.Component {
+ state = { isOne: true };
+
+ render() {
+ return (
+ }
+ renderElement={ref => }
+ />
+ );
+ }
+ }
+ wrapper = mount();
+
+ expect(document.querySelector('#test-container')).toBeTruthy();
+ expect(
+ document.querySelector('#test-container .tether-element')
+ ).toBeTruthy();
+
+ wrapper.setState({ isOne: false });
+
+ expect(document.querySelector('#test-container2')).toBeTruthy();
+ expect(
+ document.querySelector('#test-container2 .tether-element')
+ ).toBeTruthy();
+ });
+
it('passes arguments when on onUpdate() is called', () => {
const onUpdate = jest.fn();
const updateData = {
@@ -183,11 +247,14 @@ describe('TetherComponent', () => {
targetAttachment: { top: 'bottom', left: 'right' },
};
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
+
wrapper
.instance()
.getTetherInstance()
@@ -203,11 +270,14 @@ describe('TetherComponent', () => {
bar: 'bar',
};
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
+
wrapper
.instance()
.getTetherInstance()
diff --git a/tests/unit/proptypes.test.js b/tests/unit/proptypes.test.js
index 2aadbf3..8e57415 100644
--- a/tests/unit/proptypes.test.js
+++ b/tests/unit/proptypes.test.js
@@ -5,24 +5,25 @@ describe('propTypes', () => {
describe('children', () => {
const childrenProp = TetherComponent.propTypes.children;
- it('should return an error if it has no children', () => {
+ it('should return an error if children are used', () => {
const err = childrenProp(
- { children: null },
+ { children: [1, 2] },
'children',
'TetherComponent'
);
expect(err).toBeInstanceOf(Error);
- expect(err.toString()).toContain('expects at least one child');
+ expect(err.toString()).toContain(
+ 'TetherComponent no longer uses children to render components'
+ );
});
- it('should return an error if it has more than 2 children', () => {
+ it('should not return an error if there are no children', () => {
const err = childrenProp(
- { children: [1, 2, 3] },
+ { children: null },
'children',
'TetherComponent'
);
- expect(err).toBeInstanceOf(Error);
- expect(err.toString()).toContain('Only a max of two children allowed');
+ expect(err).toBeUndefined();
});
});
diff --git a/tests/unit/public.test.js b/tests/unit/public.test.js
index 29367f7..46eea67 100644
--- a/tests/unit/public.test.js
+++ b/tests/unit/public.test.js
@@ -5,10 +5,11 @@ import TetherComponent from '../../src/react-tether';
let wrapper;
const render = () => {
wrapper = mount(
-
-
-
-
+ }
+ renderElement={ref => }
+ />
);
return wrapper;
};