|
1 | 1 | # React Integration
|
2 | 2 |
|
3 |
| -Use Custom Elements in your React components without resorting to hacks. |
| 3 | +Converts web components into React components so that you can use them as first class citizens in your React components. |
4 | 4 |
|
5 |
| -## Why |
| 5 | +- Web components become lexically scoped so you can use them as tag names in JSX. |
| 6 | +- Listen for custom events triggered from web components declaratively using the standard `on*` syntax. |
| 7 | +- Passes React `props` to web components as properties instead of attributes. |
| 8 | +- Works with any web component library that uses a standard native custom element constructor, not just Skate or native web components. |
6 | 9 |
|
7 |
| -For whatever reason you want to be able to use Custom Elements within your React components and be able to pass content to the Custom Elements. For example: |
| 10 | +## Usage |
8 | 11 |
|
9 |
| -```js |
10 |
| -const CustomElement = document.registerElement('custom-element', { |
11 |
| - prototype: Object.create(HTMLElement.prototype, { |
12 |
| - createdCallback () { |
13 |
| - // Just going to remove the elements but it's likely one would template |
14 |
| - // the elements and if it moves them, it would have the same effect. |
15 |
| - this.innerHTML = ''; |
16 |
| - } |
17 |
| - }) |
18 |
| -}); |
| 12 | +Web components are converted to React components simply by passing them to the main react-integration function: |
19 | 13 |
|
20 |
| -const ReactComponent = React.createClass({ |
21 |
| - render () { |
22 |
| - return ( |
23 |
| - <div> |
24 |
| - <custom-element> |
25 |
| - <div> |
26 |
| - <AnotherReactComponent /> |
27 |
| - </div> |
28 |
| - </custom-element> |
29 |
| - </div> |
30 |
| - ); |
31 |
| - } |
32 |
| -}); |
33 |
| -``` |
| 14 | +```js |
| 15 | +import reactify from 'skatejs-react-integration'; |
34 | 16 |
|
35 |
| -When `<custom-eleent>` removes its content, React loses track of the nodes it originally rendered and throws an error when the state changes. |
| 17 | +// Create your constructor. |
| 18 | +const MyComponent = class MyComponent extends HTMLElement {}; |
36 | 19 |
|
37 |
| -## How |
| 20 | +// Define your custom element. |
| 21 | +const CustomElement = window.customElements.define('my-component', MyComponent); |
38 | 22 |
|
39 |
| -To get around this, we supply a function that will wrap the custom element in a React component that creates a new diff tree. This method is based on a React Training post by Ryan Florence about [Portals](https://github.com/ryanflorence/react-training/blob/gh-pages/lessons/05-wrapping-dom-libs.md#portals). |
| 23 | +// Reactify it! |
| 24 | +export default reactify(CustomElement); |
| 25 | +``` |
40 | 26 |
|
41 |
| -The convention is this: |
| 27 | +Usage with [SkateJS](https://github.com/skatejs/skatejs) is pretty much the same, Skate just makes defining your custom element easier: |
42 | 28 |
|
43 |
| -- Call a function passing in your custom element constructor. |
44 |
| -- The React component passes the real DOM node of the new render tree to a property. |
45 |
| -- You do the rest. |
| 29 | +```js |
| 30 | +import reactify from 'skatejs-react-integration'; |
46 | 31 |
|
| 32 | +export default reactify(skate('my-component', {})); |
| 33 | +``` |
47 | 34 |
|
48 |
| -### Static example |
| 35 | +### Lexical scoping |
49 | 36 |
|
50 |
| -The following example simply takes the `content` property value and renders it inside a div. |
| 37 | +When you convert a web component to a React component, it returns the React component. Therefore you can use it in your JSX just like any other React component. |
51 | 38 |
|
52 | 39 | ```js
|
53 |
| -import reactify from 'skatejs-react-integration'; |
| 40 | +const ReactComponent = reactify(WebComponent); |
| 41 | +ReactDOM.render(<ReactComponent />, container); |
| 42 | +``` |
54 | 43 |
|
55 |
| -const CustomElement = skate('custom-element', { |
56 |
| - render: function (elem) { |
57 |
| - const div = document.createElement('div'); |
58 |
| - div.appendChild(elem.content); |
59 |
| - elem.appendChild(div); |
60 |
| - } |
61 |
| -}); |
| 44 | +### Custom events |
62 | 45 |
|
63 |
| -export default reactify(CustomElement); |
| 46 | +Out of the box, React only works with built-in events. By using this integration layer, you can listen for custom events on a web component. |
| 47 | + |
| 48 | +```js |
| 49 | +<MyComponent oncustomevent={handler} /> |
64 | 50 | ```
|
65 | 51 |
|
| 52 | +Now when `customevent` is emitted from the web component, your `handler` will get triggered. |
66 | 53 |
|
67 |
| -### Updating example |
| 54 | +### Web component properties |
68 | 55 |
|
69 |
| -This will not update if the state changes, however. To do that we need to define a setter for the `content` property. |
| 56 | +When you pass down props to the web component, instead of setting attributes like React normally does for DOM elements, it will set all `props` as properties on your web component. This is useful because you can now pass complex data to your web components. |
70 | 57 |
|
71 | 58 | ```js
|
72 |
| -skate('custom-element', { |
73 |
| - properties: { |
74 |
| - content: { |
75 |
| - set (elem, data) { |
76 |
| - const container = elem.children[0]; |
77 |
| - const reactTree = data.newValue; |
78 |
| - container.innerHTML = ''; |
79 |
| - if (reactTree) { |
80 |
| - container.appendChild(reactTree); |
81 |
| - } |
82 |
| - } |
83 |
| - } |
84 |
| - }, |
85 |
| - render: function (elem) { |
86 |
| - const div = document.createElement('div'); |
87 |
| - div.appendChild(elem.content); |
88 |
| - elem.appendChild(div); |
89 |
| - } |
90 |
| -}); |
| 59 | +<MyComponent items={[ 'item1', 'item2' ]} callback={function() {}} /> |
91 | 60 | ```
|
92 | 61 |
|
93 |
| -You'll notice that since the diff tree ends in the ancestor tree of `<custom-element>` and the new tree begins at the React tree that was passed in to `content`, we are smashing the DOM at the `container` level. For smaller components this may not matter, but if your component is expecting updates then it'd be good to take accessibility and DOM performance into consideration. |
| 62 | +### Children |
94 | 63 |
|
| 64 | +If your web component renders content to itself, make sure you're using Shadow DOM and that you render it to the shadow root. If you do this `children` and props get passed down as normal and React won't see your content in the shadow root. |
95 | 65 |
|
96 |
| -### Diffing example |
| 66 | +### Injecting React and ReactDOM |
97 | 67 |
|
98 |
| -In order to diff and patch the real DOM tree, we'll need something that can work with real DOM, not vDOM. In the next example we'll use [skatejs-dom-diff](https://github.com/skatejs/dom-diff). |
| 68 | +By default, the React integration will look for `React` and `ReactDOM` on the window. However, this isn't the case for all apps. If you're using ES2015 modules or CommonJS, you'll have to inject them into the reactify function as options: |
99 | 69 |
|
100 | 70 | ```js
|
101 |
| -skate('custom-element', { |
102 |
| - properties: { |
103 |
| - content: { |
104 |
| - set: skate.render |
105 |
| - } |
106 |
| - }, |
107 |
| - render: skateDomDiff.render(function (elem) { |
108 |
| - const div = document.createElement('div'); |
109 |
| - div.appendChild(elem.content); |
110 |
| - return div; |
111 |
| - }) |
| 71 | +import reactify from 'skatejs-react-integration'; |
| 72 | +import React from 'react'; |
| 73 | +import ReactDOM from 'react-dom'; |
| 74 | + |
| 75 | +export default reactify(..., { |
| 76 | + React, |
| 77 | + ReactDOM |
112 | 78 | });
|
113 | 79 | ```
|
114 | 80 |
|
115 |
| -What's nice about using `skatejs-dom-diff` is that it also works with virtual DOM (or a mixture of both) and it comes with a light-weight virtual DOM interface bundled with it. If you wanted to use the previous example with JSX, all you'd need to do is the following. |
| 81 | +### Multiple React versions |
116 | 82 |
|
117 |
| -```js |
118 |
| -skate('custom-element', { |
119 |
| - properties: { |
120 |
| - content: { |
121 |
| - set: skate.render |
122 |
| - } |
123 |
| - }, |
124 |
| - render: skateDomDiff.render(function (elem, React) { |
125 |
| - return <div>{elem.content}</div>; |
126 |
| - }) |
127 |
| -}); |
128 |
| -``` |
| 83 | +The integration sets a peer-dependency on React so you know what it's compatible with. That said, you still need to be mindful that the version of React you provide to the integration layer is correct. |
0 commit comments