Skip to content

Commit bc0c5ca

Browse files
committed
Add new withElementPortal HOC (fixes #4)
1 parent da25c62 commit bc0c5ca

File tree

6 files changed

+157
-4
lines changed

6 files changed

+157
-4
lines changed

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,34 @@ Then our `CoolHeaderComponent` from the example above would receive the followin
110110
}
111111
```
112112

113+
## Usage as Higher Order Component
114+
`ElementPortal` can also be used as a [HOC](https://facebook.github.io/react/docs/higher-order-components.html):
115+
116+
```js
117+
import { withElementPortal } from 'react-element-portal';
118+
import MyComponent from 'my-component';
119+
120+
const MyComponentWithPortal = withElementPortal(MyComponent);
121+
122+
ReactDOM.render(
123+
<MyComponentWithPortal id="user" />,
124+
document.getElementById('app')
125+
);
126+
```
127+
128+
or composing with other HOC's:
129+
130+
```js
131+
import { withElementPortal } from 'react-element-portal';
132+
import { compose, connect } from 'react-redux';
133+
134+
const MyComponent = (props) => <h1>Hello, {props.name}!</h1>;
135+
136+
const MyComposedComponent = compose(
137+
withElementPortal,
138+
connect((state) => ({ name: state.name }))
139+
)(MyComponent);
140+
```
141+
113142
## Passing context to your ElementPortal
114143
Context from your main tree is passed down automatically to your `ElementPortal`. For example, if you use Redux, the `store` context will not get lost, and using `connect` will behave as expected in the children of your `ElementPortal`.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "react-element-portal",
33
"version": "1.0.0",
44
"description": "Render a React component inline, but target a DOM element (or elements) by id or selector.",
5-
"main": "lib/ElementPortal.js",
5+
"main": "lib/index.js",
66
"scripts": {
77
"test": "ava test",
88
"prebuild": "rm -rf lib && mkdir -p lib",

src/ElementPortal.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const getNodeData = (node) => {
2727
};
2828

2929
const ElementPortal = React.createClass({
30-
3130
propTypes: {
3231
id: PropTypes.string,
3332
selector: PropTypes.string,

src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import ElementPortal from './ElementPortal';
2+
import withElementPortal from './withElementPortal';
3+
4+
export { withElementPortal };
5+
export default ElementPortal;

src/withElementPortal.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import ElementPortal from './ElementPortal';
3+
4+
function getDisplayName(Component) {
5+
return Component.displayName || Component.name || 'Component';
6+
}
7+
8+
const withElementPortal = (Child) => {
9+
const Portal = ({ id, selector, shouldReset, ...childProps }) => {
10+
const ChildWrapper = (props) => <Child {...childProps} {...props} />;
11+
return <ElementPortal id={id} selector={selector} shouldReset={shouldReset} view={ChildWrapper} />;
12+
};
13+
14+
Portal.displayName = `WithElementPortal(${getDisplayName(Child)})`;
15+
16+
return Portal;
17+
};
18+
19+
export default withElementPortal;

test/ElementPortal.js

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import test from 'ava';
22
import React from 'react';
33
import { render } from 'react-dom';
44
import { connect, Provider } from 'react-redux';
5-
import { createStore } from 'redux';
5+
import { compose, createStore } from 'redux';
66

77
import 'babel-core/register';
88

99
import uniqueId from './helpers/uniqueId';
10-
import ElementPortal from '../src/ElementPortal';
10+
import ElementPortal, { withElementPortal } from '../src';
1111

1212
test('can render to ElementPortal using element id', t => {
1313
const node = document.createElement('div');
@@ -174,3 +174,104 @@ test('transfers context to the portal', t => {
174174
store.dispatch({type: 'INC'});
175175
t.is(document.getElementById(headerId).textContent, '1');
176176
});
177+
178+
test('can be used as higher-order component', t => {
179+
const node = document.createElement('div');
180+
document.body.appendChild(node);
181+
const headerId = uniqueId();
182+
const appId = uniqueId();
183+
node.innerHTML = `
184+
<div id="${headerId}">
185+
</div>
186+
<div id="${appId}">
187+
</div>
188+
`;
189+
const Greeting = () => (<div>Hello</div>);
190+
const GreetingWithPortal = withElementPortal(Greeting);
191+
192+
render(
193+
<div>
194+
<GreetingWithPortal id={headerId} />
195+
</div>,
196+
document.getElementById(appId)
197+
);
198+
t.is(document.getElementById(headerId).textContent, 'Hello');
199+
});
200+
201+
test('can be composed with other HOC\'s', t => {
202+
const store = createStore((state = { name: 'world' }) => {
203+
return state;
204+
});
205+
const node = document.createElement('div');
206+
document.body.appendChild(node);
207+
const headerId = uniqueId();
208+
const appId = uniqueId();
209+
node.innerHTML = `
210+
<div id="${headerId}">
211+
</div>
212+
<div id="${appId}">
213+
</div>
214+
`;
215+
216+
const MyComponent = (props) => <h1>Hello, {props.name}!</h1>;
217+
218+
const MyComposedComponent = compose(
219+
withElementPortal,
220+
connect((state) => ({ name: state.name }))
221+
)(MyComponent);
222+
223+
render(
224+
<Provider store={store}>
225+
<MyComposedComponent id={headerId} />
226+
</Provider>,
227+
document.getElementById(appId)
228+
);
229+
230+
t.is(document.getElementById(headerId).textContent, 'Hello, world!');
231+
});
232+
233+
test('passes along data attributes when used as HOC', t => {
234+
const node = document.createElement('div');
235+
document.body.appendChild(node);
236+
const headerId = uniqueId();
237+
const appId = uniqueId();
238+
node.innerHTML = `
239+
<div id="${headerId}" data-name="Joe">
240+
</div>
241+
<div id="${appId}">
242+
</div>
243+
`;
244+
const Greeting = ({data}) => (<div>Hello {data.name}</div>);
245+
const GreetingWithPortal = withElementPortal(Greeting);
246+
247+
render(
248+
<div>
249+
<GreetingWithPortal id={headerId} />
250+
</div>,
251+
document.getElementById(appId)
252+
);
253+
t.is(document.getElementById(headerId).textContent, 'Hello Joe');
254+
});
255+
256+
test('passes props through to the inner component when used as a HOC', t => {
257+
const node = document.createElement('div');
258+
document.body.appendChild(node);
259+
const headerId = uniqueId();
260+
const appId = uniqueId();
261+
node.innerHTML = `
262+
<div id="${headerId}">
263+
</div>
264+
<div id="${appId}">
265+
</div>
266+
`;
267+
const Greeting = ({name}) => (<div>Hello {name}</div>);
268+
const GreetingWithPortal = withElementPortal(Greeting);
269+
270+
render(
271+
<div>
272+
<GreetingWithPortal id={headerId} name="Joe" />
273+
</div>,
274+
document.getElementById(appId)
275+
);
276+
t.is(document.getElementById(headerId).textContent, 'Hello Joe');
277+
});

0 commit comments

Comments
 (0)