{"id":15046852,"url":"https://github.com/christophp/web-react-components","last_synced_at":"2025-08-21T20:32:57.142Z","repository":{"id":21817747,"uuid":"93948667","full_name":"ChristophP/web-react-components","owner":"ChristophP","description":"Reuse your react components, for example in Elm or any other technology by wrapping them easily into a Web Component.","archived":false,"fork":false,"pushed_at":"2023-04-18T14:46:46.000Z","size":674,"stargazers_count":58,"open_issues_count":16,"forks_count":5,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-12-16T02:11:35.684Z","etag":null,"topics":["elm","react","webcomponents"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChristophP.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-06-10T15:36:36.000Z","updated_at":"2023-07-22T20:49:03.000Z","dependencies_parsed_at":"2024-06-18T18:16:14.835Z","dependency_job_id":"db1f2a96-5022-45b4-b5ce-31e51b75b047","html_url":"https://github.com/ChristophP/web-react-components","commit_stats":{"total_commits":124,"total_committers":5,"mean_commits":24.8,"dds":0.3951612903225806,"last_synced_commit":"ebdfe7e804a25b41b5b594a50da66514343f2cb4"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristophP%2Fweb-react-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristophP%2Fweb-react-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristophP%2Fweb-react-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChristophP%2Fweb-react-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChristophP","download_url":"https://codeload.github.com/ChristophP/web-react-components/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230532448,"owners_count":18240792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elm","react","webcomponents"],"created_at":"2024-09-24T20:53:39.787Z","updated_at":"2024-12-20T04:07:54.634Z","avatar_url":"https://github.com/ChristophP.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Web React Components\n\nPut your React Components into a neat Web components wrapper and render them\nanywhere, not just in your React code.\n\n## Motivation\n\nWe have lots of React code and really wanted to write Elm. Putting React inside Elm\nis not trivial and not being able to use our tried-and-tested components\nwould have been a big reason against using Elm.\nSo after watching Richard Feldman's [talk](https://www.youtube.com/watch?v=ar3TakwE8o0)\nwe thought \"what if Elm rendered just Web Components and the Web Components render\nwhatever they want inside(in our case React)\". So how to convert all of our React\ncomponents into Web Components? Well, that is what this repo is for.\n\n## Dependencies\n\nThis package requires the following dependencies:\n\nPolyfills:\nThese polyfills are needed for this to work in all evergreen browsers(including IE11).\nWe use polyfills for [Web Components V1](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements).\n\n- Polyfills for Web Components features, namely custom elements(CE) and shady DOM(SD)\n- Adapter to transform non-native ES2015 classes into true ES2015 classes(needed for CE)\n- Everything you need to provide an ES2015 environment in the browser\n\nLibraries:\n- React\n- ReactDOM\n\nIf you don't want to assemble all these polyfills yourself and just want to get\nstarted quickly, just drop these script tags into your page. They contain everything\nyou need to get going.\n\n**NOTE: even if you use Chrome which supports Web Components, you will still need\nthe `custom-elements-es5-adapter`.**\n\n\n```html\n\u003cscript src=\"//cdn.polyfill.io/v2/polyfill.min.js?features=default,fetch,es6,Array.prototype.includes\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.1/custom-elements-es5-adapter.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.1/webcomponents-sd-ce.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-with-addons.js\"\u003e\u003c/script\u003e\n\u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.js\"\u003e\u003c/script\u003e\n```\n\n## Usage\n\n```sh\nnpm install -S web-react-components\n```\n\nThis module will expose functions to hook up your React component with a web component.\n\nThen in your code, import the registering function:\n```js\nimport React from 'react';\n// import the registering function\nimport { register } from 'web-react-components';\n\nconst YourComponent = ({ name, isDisabled,  onButtonClick }) =\u003e (\n  \u003cbutton disabled={isDisabled} onClick={onButtonClick}\u003e\n    {`Hello ${name}, please click me`}\n  \u003c/button\u003e\n);\n\n// call it to register the web component\n// this will transform all \u003cyour-component\u003e-tags in the markup\n// ATTENTION: all custom element tag names MUST contain a dash\n// use it anywhere like this:\n// \u003cyour-component name=\"Peter\" isDisabled onClick=\"console.log('hello')\"\u003e\u003c/your-component\u003e\nregister(YourComponent, 'your-component', [\n  // these attribute values will be json parsed\n  'name',\n  // this will define a boolean attribute\n  '!!isDisabled',\n  ],\n  // handlers can be created here. The functions of the object can return any\n  // new instance of `Event` or `CustomEvent`\n  {\n    onButtonClick: e =\u003e new Event('buttonclick', { bubbles: true }),\n  }\n);\n```\n\n## API\n\n### Register\n\nThe `register` function takes a ReactComponent and registers it using\n`customElements.define(...)`.\n\n\n```\nregister(reactComponent, nodeName, propsList, eventMappers = {})\n```\n| Param | Description |\n| --- | --- |\n| `reactComponent` | An actual React Component Class / Stateless Function |\n| `nodeName` | A tag name for the web component to be created (Must contain a dash)|\n| `propsList` | An array of strings which represents the props that should be wired up to React. There are 3 ways to declare a prop. \u003cbr\u003e- `'propName'`: with a regular name the attribute value be JSON parsed and passed to React, if that fails, then it will be passed as a String. This let you pass arbitrary data to the React component, even through DOM attributes.\u003cbr\u003e- `'!!propName'`: With leading bangs this property will be considered a boolean and pass `true` to React or `false` if the attribute is not present on the DOM node.\u003cbr\u003e- `'propName()'`: With trailing parens the property will be considered an event handler and set up event proxying between react and the DOM, so that it's possible to listen to React props handlers from the DOM.|\n| `eventMappers` | An optional object with function values. The keys are handler property names (e.g. `onChange`) and the values are functions with the following signature `(...args) =\u003e Event\\|null`. The returned event will then be dispatched on the Web Component. If null is returned nothing is dispatched. *Note: EventMappers will override any event definitions in the propertyList parameter.* |\n| `options` | An optional object. The only options right now is `useShadowDOM` which defaults to true. You can opt out of using shadow DOM by setting this to false. |\n| returns `WebComponent class` | An class which is already registered using `customElements.define(...)` |\n\n### Convert\n\nConvert is almost the same as `register` except you have to register the\nComponent yourself. Do this when you want further extend the component before\nregistering it.\n\n```\n// This function will return you a webcomponent instance\nconvert(reactComponent, propsList, eventMappers = {})\n```\n| Param | Description |\n| --- | --- |\n| `reactComponent` | An actual React Component Class / Stateless Function |\n| `propsList` | An array of strings which represents the props that should be wired up to React. There are 3 ways to declare a prop. \u003cbr\u003e- `'propName'`: with a regular name the attribute value be JSON parsed and passed to React, if that fails, then it will be passed as a String. This let you pass arbitrary data to the React component, even through DOM attributes.\u003cbr\u003e- `'!!propName'`: With leading bangs this property will be considered a boolean and pass `true` to React or `false` if the attribute is not present on the DOM node.\u003cbr\u003e- `'propName()'`: With trailing parens the property will be considered an event handler and set up event proxying between react and the DOM, so that it's possible to listen to React props handlers from the DOM.|\n| `eventMappers` | An optional object with function values. The keys are handler property names (e.g. `onChange`) and the values are functions with the following signature `(...args) =\u003e Event\\|null`. The returned event will then be dispatched on the Web Component. If null is returned nothing is dispatched. *Note: EventMappers will override any event definitions in the propertyList parameter.* |\n| `options` | An optional object. The only options right now is `useShadowDOM` which defaults to true. You can opt out of using shadow DOM by setting this to false. |\n| returns `WebComponent class` | An class which can then be used to register using `customElements.define(...)` |\n\nYou can the use it like this:\n```js\nimport { convert } from 'web-react-components';\n\nconst MyComponent = convert(MyReactComponet, ['name', 'type'],\n  {\n    onChange: (e) =\u003e new Event('crazyChange', { bubbles: true }),\n  });\n\n// register it here\ncustomElements.define('my-component', MyComponent);\n```\n## Examples\n\nThen you can render the component from anywhere (even Elm, React, plain HTML, Angular if you really have to :-))\n\nElm:\n```elm\n-- In the view do this:\n...\ntype Msg\n   = ...\n   | ButtonClick\n\n{-| Define a shortcut for your component -}\nyourComponent : List (Attribute msg) -\u003e List (Html msg) -\u003e Html msg\nyourComponent = node \"your-component\"\n\nview model = div [] [\n  yourComponent\n    [ attribute \"name\" \"Peter\"\n    , property \"isDisabled\" (Json.Encode.bool True)\n    , on \"buttonclick\" (Decode.succeed ButtonClick)\n    ]\n    [ span [style (\"color\", \"green\")] [text \"Click Me\"]\n  ]\n]\n```\n\nPlain HTML:\n```html\n...\n\u003cdiv\u003e\n  \u003c!-- render your component like this--\u003e\n  \u003cyour-component name=\"Peter\" isDisabled onButtonClick=\"console.log('you can also use `addEventListener` to attach events')\"\u003e\n    \u003cspan style=\"color: green;\"\u003eClick Me\u003c/span\u003e\n  \u003c/your-component\u003e\n\u003c/div\u003e\n```\n\n## Passing props\n\n### Attributes and Properties\nSince in HTML attribute values can only be strings, other values need to be\nencoded. The created web component will try a `JSON.parse()` on each attribute, so all\nJSON values are valid inside the string. If the parsing fails the value will\njust be passed to React as a string.\n\n`Example: passing '{ \"name\": Peter }' is fine.`\n\nFor each attribute you register, a matching property will be defined on the DOM\nnode. These properties will have getters and setters that automatically do JSON\nparsing and updating the corresponding attribute as well.\n\nYou can also use JS to pass properties like this:\n```js\ndocument.getElementById('your-dom-id').numbers = [1, 2, 3, 4];\n```\n\n### Events\n\nEvents can be listened to in 3 different ways that you should be familiar with from\nthe DOM.\n\n```js\n// with `addEventListener()`. The event name will be used here, so use the type of\nthe returned event from the `eventMappers` parameter.\ndocument.getElementById('#my-component').addEventListener('change', function() { ... }, false);\n\n// with the DOM Property (notice the uppercase `C`, because the name has to be the same as\n// the property in React)\ndocument.getElementById('#my-component').onChange = function() { ... };\n\n// with the HTML Attributes\n\u003ccustom-component onChange=\"console.log('Hello')\"\u003e\u003c/custom-component\u003e\n```\nTo access data from the original event from React you will have to\ndo something like this:\n\n```js\ndocument.getElementById('#my-component').addEventListener('change', function(event) {\n  // data is an array of arguments that were passed to the react event handler\n  const data = event.detail;\n  // log the first arg of the react event handler\n  console.log(data[0]);\n}, false);\n```\n\n### Children\nChildren are passed like you would expect by simple add child nodes to the\nelement or programmatically changing the `innerHMTL` or `childNodes` of a\ncustom compoenent.\n\nThe children will be part of the shadow DOM of the custom components and are rendered\ninto a [`\u003cslot\u003e`-tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot).\nThat `\u003cslot\u003e` will be passed to the React components as `children`,\nthat you can render wherever you want.\n\n```html\n\u003ccustom-component onClick=\"console.log('Hello')\"\u003e\n  \u003cspan\u003eI am a child\u003c/span\u003e // will be passed as `children` to React\n\u003c/custom-component\u003e\n```\n\n## What about CSS?\n\nSince the React components, which are wrapped by the Web Component, will live in the\n[shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM),\nglobal css will not have any effect on them(at least in browsers, which are correcly\nimplementing it). Thus we recommend shipping the components with inline styles, an internal stylesheet, or\nor if you want to include an external stylesheet, use an `@import` declaration in\nan internal style tag, like this.\n\n```html\n// inside render method of your React component\n\u003cstyle\u003e\n  @import url('path/to/stylesheeet.css');\n\u003c/style\u003e\n```\n\n## How does it work under the hood?\n\nFor the ultimate source of truth, the source code is pretty much all this this\n[file](https://github.com/ChristophP/web-react-components/blob/master/src/index.js).\n\nBut here is a quick write-up:\n\nThe whole React component will be inserted into the Shadow DOM.\nFor each property that is declared with the exposed register function, a DOM\nattribute is created, that is being listened to for changes through\nthe [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements).\nAlso, a corresponding DOM node property is set up with getters and setters, that\nkeeps the property and the attribute in sync. Registering a property with a\nleading `!!` will declare a boolean attribute. Then the getters and setters\nwill work slightly different and pass a boolean value to React depending on the\nexistence of the attribute.\n\nWhen a property is registered with a trailing `()`, a handler will be created.\nA handler is attached to the wrapped React component, that will\ntrigger a [`CustomEvent`](https://developer.mozilla.org/de/docs/Web/API/CustomEvent)\non the actual web component DOM node and proxy data data to the web component.\nThis allows you to listen to react event simply by listening to DOM events.\n\nChildren of the web component somehow have to be inserted into the children\nof the React components. For this, we use a `\u003cslot\u003e`-tag, which is standard\nweb component shadow DOM technology and built to handle cases like that.\n\n## More Examples\n\nYou can see an example [here](https://github.com/ChristophP/web-react-components/blob/master/dev-assets/index.html).\nYou can also clone the repo and run `npm i` and `npm start`.\nOpen your browser at `http://localhost:8080`\n\nThere are even more examples in the\n[`examples` folder](https://github.com/ChristophP/web-react-components/tree/master/examples).\n\n## Contributing\n\nPRs are highly welcome. If you need features or find bugs please submit an\nissue.\n\n## Credits\n\nMade with countless hours of bouncing around ideas with `layflags`. Also intially\ninspired by talks with `tkreis` and `rtfeldman`, `tomekwi` at the Elm Europe 2017.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristophp%2Fweb-react-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchristophp%2Fweb-react-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchristophp%2Fweb-react-components/lists"}