How to create a redistributable widget using React - reactjs

I want to build a widget using React that can be consumed by non-spa sites by pointing to a js file and adding a javascript snippet. E.g.
<script src="somepath/mycomponent.min.js"></script>
<script>MyComponent.render('id-of-a-div')</script>
I have created a component with React and setup webpack. I most spa-cases the top-level component ends with
ReactDOM.render(<MyComponent/>, document.getElementById(id));
My component won't be able to do this since it doesn't know to which element to render itself.
How and where should I create the function that would attach the component to an element?

you need a file with the methods that initialize and interacts with the parent page.
Maybe in utilities.js
export const utilities = {
// Here you will initialize the widget and you could pass an argument
// with the id of the tag where you wanna inject the app
initWidget: ( elementId ) => {
if( !elementId ){
document.createElement( 'rootApp' );
ReactDOM.render(<MyComponent/>, document.getElementById('rootApp'));
}
else {
ReactDOM.render(<MyComponent/>, document.getElementById(id));
}
},
}
And the utility will work in the tag of html to initilize it right there after all the js is loaded:
<script type='text/javascript' src='/widget.js'></script>
<script>
utilities.initWidget( 'myCustomId' );
</script>
Maybe a flow like this could be useful to you.

Related

How to cast an HTMLElement to a React element?

I am working on a React application that uses an external library in Vanilla JS that creates DOM elements using document.createElement(...), some of this data I want to insert into my React elements. If I have a simple application:
let sub = React.createElement('p', null, 'Made with React'),
main = React.createElement('div', null, [sub])
React.render(main, document.getElementById('app'));
It renders with the content <p data-reactid=".0.0">Made with React</p>. If I try it with document.createElement instead, nothing is rendered.
let sub = document.createElement('p');
sub.innerText = 'Not React';
let main = React.createElement('div', null, [sub])
React.render(main, document.getElementById('app'));
I have currently found a workaround using dangerouslySetInnerHTML.
let sub = document.createElement('p');
sub.innerText = 'Not React';
let main = React.createElement('div', {dangerouslySetInnerHTML: {__html: sub.outerHTML}});
React.render(main, document.getElementById('app'));
Which renders but this doesn't seem safe. What is the proper way to append HTMLElement as React child elements?
Here's an elegantly simple solution I discovered to include vanilla HTML Elements to a React project using refs:
class Container extends React.Component {
render() {
return <div ref={ ref => ref.appendChild(this.props.child) }></div>;
}
}
Assume you have a vanilla paragraph element you want to display:
const para = document.createElement('p');
para.innerText = 'Hello world!';
Just create a container for it like this to render alongside your other React elements:
<Container child={ para }/>
dangerouslySetInnerHTML can be used safely with HTML sanitizers. Lots of libraries are available to prevent XSS.
React.createElement() returns virtual dom javascript object, document.createElement() returns dom object.
We are using javascript objects to manage to diff efficiently and update costly dom, we would not want to fallback to dom elements unless explicitly needed,

Create Html from React Component including Style

I have a simple react component in which using ref I am getting the div but I have to generate a html includes styling as well. So I can pass this html to PDF generation backend server.
pdfRef(elem){
console.log(elem);
//<div><span>dataPDF</span> </div>
}
render() {
return (
<div ref={(elem) => this.pdfRef(elem)} className="SomeCssClass">
<span >dataPDF</span>
</div>
);
}
}
[Edit]
When I try to print the div via ref, the elements are printed with class name. But when I send this string to pdf service, since only html element is sent and class name without the actual css , the pdf is generated without style.
is there any way to generate html with css as as string so further it can be send to pdf service. Hope the question is clear
Any pointers?
The server.js script in react-dom lets you render a React component to static html string. You can require it in your code like:
const ReactDOMServer = require('react-dom/server');
Or using ES6 syntax:
import ReactDomServer from 'react-dom/server'
After this you can render your component to HTML string using the ReactDomServer.renderToString or ReactDomServer.renderToStaticMarkup functions as follows:
const htmlStr = ReactDomServer.renderToStaticMarkup(<MyComponent prop1={'value1'} />);
This looks almost exactly like ReactDom.render, except that it doesn't need the second parameter of dom node to render at, and the html string is returned. Additionally this method can be used on both client and server side. For your generate-pdf use case renderToStaticMarkup would suffice. If required check the documentation at following link for subtle difference between the two methods mentioned above: https://reactjs.org/docs/react-dom-server.html

Integrating a Skyscanner widget with React

I would like to include a Skyscanner Widget within my React application.
https://partners.skyscanner.net/affiliates/widgets-documentation/basic-and-location-widgets
<div data-skyscanner-widget="BasicWidget" data-locale="en-GB" data-params="colour:glen"></div>
<script src="https://widgets.skyscanner.net/widget-server/js/loader.js" async></script>
As the script is stored externally, how would I go about integrating this within my react application.
Thanks in advance.
You will have to add this script tag to your .hmtl file:
<script src="https://widgets.skyscanner.net/widget-server/js/loader.js"></script>
Then you can try to use this Component:
class MyWidget extends React.Component {
componentDidMount() {
window.skyscanner.widgets.load();
}
render() {
return (
<div
data-skyscanner-widget="FlightSearchWidget"
data-locale="en-GB"
/>
)
}
}
It will work in production and development mode.
fiddle
There are several ways to include the external script to RIA.
load-script library (https://www.npmjs.com/package/load-script). Which can help you to load script dynamically. You can create a special Component and load script after mounting:
class ScyScanner extends Component {
componentDidMount() {
loadScript('URL')
}
render() {
return (
<div data-skyscanner-widget="BasicWidget" data-locale="en-GB" data-params="colour:glen"></div>
)
}
}
The next way is to create a tag for skyscanner-widget manually in the index.html document if you use it and attach external script by adding script tag. It's simpler but less flexible.
Also, you can create a function-helper which loads external script dynamically as soon as it's called:
export function loadExternalScripts() {
if (env === 'production') loadScript('URL')
}
— then, your script can perform the loading script according to the environment.
As you have a link to JS loader (https://widgets.skyscanner.net/widget-server/js/loader.js). That means you don't care about script versions.

Client-side React with Webpack - custom configuration via Parameters

I have used create-react-app to create a new React project. I'm pretty much done and want to deploy the app as a JS plug-in running in the browser.
In the code index.js:
ReactDOM.render(<Comments articleId="123" />, document.getElementById('comments'));
is after been built compiled into main.xxxx.chunk.js:
[...] c.a.render(s.a.createElement(R,{articleId:123}),document.getElementById("comments"))}},[[21,2,1]]]);
But I want to take the value over somehow as a parameter:
<!-- index.html -->
<div id="comments">Rendering comments...</div>
<script>var articleId = 123;</script>
...
<script src="/assets/js/comments/main.xxxx.chunk.js"></script>
Is something like that possible? How?
One solution I have found:
//index.js
const commentsDiv = document.getElementById('comments');
const articleId = commentsDiv.getAttribute('articleId')
ReactDOM.render(<Comments articleId={articleId} />, commentsDiv);
Then render a HTML element with the attribute:
<!-- index.html -->
<div id="comments" articleid="123">Rendering comments...</div>
you can make articleid as a global value in html.
window.articleid=123
and then inside react call window.articleid to get the id.

Globally Shared Redux Container

Summary
I want to have a global messenger that has a reference to store. Let's call it a Messenger. I need to use it across different containers. It doesn't have its counterpart React component.
What I have right now
class Messenger {
constructor(store, webview) {
if (!chrome.runtime.id) {
return false;
}
this.webview = webview;
this.store = store;
if (chrome.runtime.id) {
chrome.runtime.onMessage.addListener(this.messageHandler);
chrome.commands.onCommand.addListener(this.commandHandler);
}
}
sendMessage(msg) {
if (this.webview.contentWindow) {
this.webview.contentWindow.postMessage(msg, "*");
}
}
}
Messenger should be initialized with a store and webview objects like below:
$(function() {
// exposing sandboxMessenger to a global namespace
// so it can communicate with webview and sandbox
window.app = {};
app.sandboxMessenger = new SandboxMessenger(store, $('webview')[0]);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
});
But the above setting requires me to have the webview object completely outside of React environment like below:
<div id="app"></div> <!-- This part will be populated by React -->
<div id="webview-container">
<!-- This part is isolated because I need to find it via $('#webview')[0];
and pass it to Messenger -->
<webview id="webview" src="sandbox/sandbox.html" partition="static"></webview>
</div>
What seems ideal to me
I want to contain webview-container DOM into a separate Webview React Component. When it is mounted, I will initialize Messenger because I have a webview object.
Problem
I don't have store object if I tried to follow the above case. If I passed down store explicitly, then I'd have to subscribe manually to store in all of my child containers.
Question
What's the best way to deal with this specific situation, and what's a general way to implement a global Redux container?

Resources