Skip to content

Commit d818d2d

Browse files
authored
Merge pull request #3 from vitorbal/unstable-render
Breaking: use `renderSubtreeIntoContainer` for passing context
2 parents 6524c06 + d67e71e commit d818d2d

File tree

12 files changed

+105
-249
lines changed

12 files changed

+105
-249
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,3 @@ coverage
2525

2626
# Build directory
2727
/lib
28-
/react-redux-element-portal/lib
29-
/react-redux-element-portal/package.json
30-
/react-redux-element-portal/node_modules
31-
/test/helpers/ReduxElementPortal.js

.npmignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,3 @@ coverage
1414

1515
# Optional REPL history
1616
.node_repl_history
17-
18-
# Sub packages
19-
/react-redux-element-portal

README.md

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ If you're making a shiny new React app where you use React everywhere, for every
1414
npm install react-element-portal --save
1515
```
1616

17-
To get an ElementPortal bound to Redux Provider, you can use `react-redux-element-portal`.
18-
19-
```bash
20-
npm install react-redux-element-portal --save
21-
```
22-
2317
## Usage with vanilla React
2418

2519
Let's say we get this from the server:
@@ -80,56 +74,41 @@ You can also use a selector instead of an id.
8074
</ElementPortal>
8175
```
8276

83-
## Passing context to your ElementPortal
84-
85-
When we render outside your main tree, context will be lost. For example, if you use with Redux, the `store` context gets lost, so `connect` won't work on children of your `ElementPortal`. You can use `createElementPortal` to create a custom ElementPortal that passes along context.
86-
87-
An example with Redux:
77+
## Additional features
78+
The `shouldReset` prop can be used to remove any classes and styles from the DOM node we are rendering to:
8879

8980
```js
90-
import { createElementPortal } from 'redux-element-portal';
91-
import { Provider } from 'react-redux';
92-
93-
// Bind an ElementPortal to a Provider component and pass 'store' context into that provider.
94-
const ElementPortal = createElementPortal(Provider, ['store']);
95-
96-
const UserMenu = ({user}) => (
81+
// All styles and classes from the node with id "header" will be cleared
82+
<ElementPortal id="header" shouldReset>
9783
<div>
98-
<Menu>
99-
<Label>{user.firstName}</Label>
100-
<Items>
101-
<Item>Upgrade</Item>
102-
<Item>Settings</Item>
103-
<Item>Support</Item>
104-
</Items>
105-
</Menu>
84+
...
10685
</div>
107-
);
86+
</ElementPortal>
87+
```
10888

109-
const UserMenuContainer = connect(state => ({
110-
user: state.user
111-
}))(UserMenu);
89+
`ElementPortal` also accepts an optional `view` prop that takes a component, to be rendered inside the portal:
11290

113-
ReactDOM.render(
114-
<Provider store={store}>
115-
<div>
116-
<ElementPortal id="user">
117-
<UserMenuContainer/>
118-
</ElementPortal>
119-
</div>
120-
</Provider>,
121-
document.getElementById('app')
122-
);
91+
```js
92+
<ElementPortal id="header" view="CoolHeaderComponent" />
12393
```
12494

125-
Redux is just an example here. You can pass along any context with your own custom component like `Provider`. Look at the [source for Provider](https://github.com/reactjs/react-redux/blob/master/src/components/Provider.js) for reference. It's pretty simple. Just define some context that gets transferred from props to child context. Combine that with `createElementPortal` to customize your ElementPortal. If you want to use Redux _and_ some other custom context, just compose them together, creating your own Provider that internally also uses Redux's `Provider`.
95+
One advantage of using the `view` prop to specify a component is that any `data-` attributes from the DOM node the portal is rendering to will be passed along to our component as a `data` prop.
96+
For example, if the DOM node we are rendering to looks like this:
12697

127-
## React Redux Element Portal
98+
```html
99+
<div id="header" data-user-id="26742" data-name="Joe">
100+
...
101+
</div>
102+
```
128103

129-
For Redux, you don't actually have to use `createElementPortal` at all, because there's already an npm package you can use that does it for you.
104+
Then our `CoolHeaderComponent` from the example above would receive the following `data` prop:
130105

131106
```js
132-
import ElementPortal from 'react-redux-element-portal';
133-
134-
// ElementPortal is already bound to Provider.
107+
{
108+
'user-id': '26742',
109+
name: 'Joe'
110+
}
135111
```
112+
113+
## Passing context to your ElementPortal
114+
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`.

build-packages.js

Lines changed: 0 additions & 45 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,10 @@
44
"description": "Render a React component inline, but target a DOM element (or elements) by id or selector.",
55
"main": "lib/ElementPortal.js",
66
"scripts": {
7-
"pretest": "cp react-redux-element-portal/src/ElementPortal.js test/helpers/ReduxElementPortal.js",
87
"test": "ava test",
98
"prebuild": "rm -rf lib && mkdir -p lib",
109
"build": "babel src --out-dir lib",
11-
"build-packages": "babel-node build-packages.js",
12-
"publish-packages": "babel-node publish-packages.js",
1310
"prepublish": "npm run build",
14-
"postpublish": "npm run build-packages && npm run publish-packages",
1511
"patch-release": "npm version patch && npm publish && git push --follow-tags"
1612
},
1713
"repository": {
@@ -68,7 +64,5 @@
6864
"./test/helpers/setup-browser-env.js"
6965
]
7066
},
71-
"dependencies": {
72-
"invariant": "^2.0.0"
73-
}
67+
"dependencies": {}
7468
}

publish-packages.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

react-redux-element-portal/README.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

react-redux-element-portal/src/ElementPortal.js

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/ElementPortal.js

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,80 @@
1-
import createElementPortal from './createElementPortal';
1+
import React, { PropTypes } from 'react';
2+
import ReactDOM from 'react-dom';
23

3-
export { createElementPortal };
4+
const reDataAttr = /^data\-(.+)$/;
45

5-
const ElementPortal = createElementPortal();
6+
export const getNodeData = (node) => {
7+
// fallback
8+
if (!node.datset) {
9+
const result = {};
10+
const attributes = node.attributes;
11+
for (let i = 0; i < attributes.length; i++) {
12+
const attr = attributes[i];
13+
const match = attr.name.match(reDataAttr);
14+
if (match) {
15+
const key = match[1];
16+
result[key] = attr.value;
17+
}
18+
}
19+
return result;
20+
}
21+
22+
return Object.keys(node.dataset)
23+
.reduce((result, key) => {
24+
result[key] = node.dataset[key];
25+
return result;
26+
}, {});
27+
};
28+
29+
const ElementPortal = React.createClass({
30+
31+
propTypes: {
32+
id: PropTypes.string,
33+
selector: PropTypes.string,
34+
// Remove styles and classes from node.
35+
shouldReset: PropTypes.bool,
36+
view: PropTypes.func
37+
},
38+
39+
componentDidMount() {
40+
this.renderToNodes();
41+
},
42+
43+
componentDidUpdate() {
44+
this.renderToNodes();
45+
},
46+
47+
renderToNodes() {
48+
const nodeById = this.props.id && document.getElementById(this.props.id);
49+
const nodesById = nodeById ? [nodeById] : [];
50+
const nodesBySelector = (this.props.selector && [].slice.call(document.querySelectorAll(this.props.selector))) || [];
51+
const nodes = nodesById.concat(nodesBySelector);
52+
53+
nodes.forEach(node => {
54+
if (this.props.shouldReset) {
55+
node.className = '';
56+
node.removeAttribute('style');
57+
}
58+
59+
const View = this.props.view;
60+
61+
const children = View ?
62+
<View domNode={node} data={getNodeData(node)}/> :
63+
React.Children.only(this.props.children);
64+
65+
// This will become `ReactDOM.unstable_createPortal()` once the new Fiber implementation lands in React:
66+
// https://github.com/facebook/react/pull/8386
67+
ReactDOM.unstable_renderSubtreeIntoContainer(
68+
this,
69+
children,
70+
node
71+
);
72+
});
73+
},
74+
75+
render() {
76+
return null;
77+
}
78+
});
679

780
export default ElementPortal;

0 commit comments

Comments
 (0)