How to cast an HTMLElement to a React element? - reactjs

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,

Related

Open Layer Renders Map component twice in react

I'm using Open Layer to render map functionality in a react application. I used the simple code snippets to display the map in the application. Unfortunately the application renders the map twice as below.
Here is the code snippet I used to display the map:
export default function MapView() {
const view = new View({
center: [0, 0],
zoom: 2,
});
useEffect(() => {
new Map({
layers: [
new TileLayer({source: new OSM()}),
],
view: view,
target:'map',
})
},[])
return (
<>
<div id="map" style={{width:'100%',height:'400px'}}></div>
</>
)
}
And the parent component is as below
function App() {
return (
<div>
<MapView />
</div>
);
}
Your component may be mounted multiple times; useEffect with an empty deps array runs each time the component is mounted, but instantiating View/Map appears to mutate the DOM directly and inject the map. If you look at my example, the message "I'm mounting!" is printed to the console twice. Because useEffect is running once per mount, you're instantiating multiple Map objects and attaching them to the element with id map. Instead, you should consider using a ref to hold your Map reference so that only one map object is instantiated per instance of your component.
Additionally, rather than referencing an ID by string in your map mounting, you should instead use a ref for the component's rendered node, instead, and pass that reference to target (which does appear to accept an HTMLElement node directly).
See this sandbox link for an example.
This ref handle also gets you a persistent reference to the map object, which you can then use to perform imperative operations on the map in response to prop changes (see my example zoom-sensitive useEffect).

How to create a redistributable widget using React

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.

Converting HTMLElement to a React Element, and keeping any event listeners

Rendering some JSON data into a set of collapsible HTML elements is EASY using a library like renderjson (npm package) for Vanilla JS
const data = { sample: [1, 2, 3, 4], data: { a: 1, b: 2, c: ["hello", null] } };
const rjson = renderjson(data);
document.getElementById('to-render').append(rjson);
In The react world
However, renderjson returns an HTMLElement, NOT A STRING. Please note that I'm not JUST trying to convert HTML strings into react HTML here. I'm trying to convert the HTMLElement into a real ReactElement.
So, I actually managed to do this, but since I'm parsing the HTMLElement into a string, all the onClick event listeners generated by renderjson on the + and - handles (to open or collapse the json) are LOST in the process....
Does anyone have any idea on how to convert a HTMLElement object into React Element, keeping all the original event handlers?
I guess, the real question here is: how can the renderjson package be "repackaged" to become React friendly?
Here is a codesandbox to make it easier to see what I'm talking about here.
Good question, I keep running into similar issues with non-React JS libraries that return DOM elements. These libraries should be ported to React, but if you don't want to do that, you can use findDomNode() to get a DOM (not React) node, and append your HTMLElement there. Please note that in the official docs, its use is discouraged because "it pierces the component abstraction". Anyways, in your example, you could add a wrapper class:
class JsonTreeWrapper extends Component {
componentDidMount() {
const renderedJson = renderjson(this.props.data);
const container = findDOMNode(this);
container.appendChild(renderedJson);
}
render() {
return <div/>;
}
}
what this does is render a div, and when the component mounts it finds the div and adds the HTMLElement generated by your library there. Simple.
In your App, you can use it like this:
class App extends Component {
render() {
return (
<div>
<h1> Inside the react world </h1>
<JsonTreeWrapper data={data}/>
</div>
);
}
}
This way your listeners will be working just fine.
Codesandbox didn't work for me, but you can check a demo here. I hope this helps!

Rendering React components from text

I'm working on a simple blog/CMS tool. In authoring content, I'm supporting an option to enter raw html/css. The user enters this content into a text area, and I can render this into a page using dangerouslySetInnerHtml. That works fine.
However, I'd really like to embed some React components the content as well. Ideally I'd like to enter something like this into a textarea...
<div>
<p>Some content</p>
<MyPictureComponent url="..." />
</div>
...and then render that into a page and have it create the MyPictureComponent.
I'll be storing the above "code" in a database, and rendering it dynamically as users view the "post". Is it possible to rendering that raw text as functioning React?
I saw this project (HTML to React), which seemed promising, bu that seems to only parse the HTML given to it, not tags for React components.
I found a way to do what I want, with the caveat that it's somewhat manual, and potentially dangerous. However, in my case, I'm creating a blog/CMS for a very limited audience, and the concern about users potentially inserting harmful content is non-existent.
My approach ended up using html-to-react (https://www.npmjs.com/package/html-to-react). Html-to-react accepts a string (containing raw HTML markup), and transforms it into a proper React component. By default, its parse() method doesn't properly handle React components (it just turns them into lower-case-named html elements). However, the library has a parseWithInstructions, which allows you to control how individual nodes in the component are rendered.
In my case, I want to enable certain React components to be rendered. One of those is my ExternalLink component. What follows is the method I use to transform some user-entered raw HTML into a React component that properly rendered my components.
updatePreview() {
// Combine the user-entered CSS and the user-entered HTML into a single string.
let outputPreview = "<div><style>" + this.state.cssValue + "</style><div>" + this.state.inputValue + "</div></div>";
let htmlToReactParser = new HtmlToReact.Parser();
let processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React);
let processingInstructions = [
{
// Custom <ExternalLink> processing
shouldProcessNode: function (node) {
return node.name === 'externallink';
},
processNode: function (node, children) {
let attribs = node.attribs;
return <ExternalLink {...attribs}>{children}</ExternalLink>
}
},
{
// Anything else
shouldProcessNode: function (node) {
return true;
},
processNode: processNodeDefinitions.processDefaultNode
}];
// Convert the HTML into a React component
let reactComponent = htmlToReactParser.parseWithInstructions(outputPreview, () => true,
processingInstructions);
// Now that we have a react component, we set it to the state.
// Our render() method includes a "{this.state.outputPreview}", which causes the
// component to be rendered.
this.setState({outputPreview: reactComponent, refreshPreviewTimer: null});
}
Note that outputString in the first line of the method will contain some raw text like this:
"<div>
<style></style>
<div>
<p>Here's a link:<p>
<ExternalLink url="http://www.google.com">Google</ExternalLink>
</div>
</div>"
There are some approaches I'll take to generalize this approach more, using a dictionary of strings to enable support for a wider range of Components. I'll also look at some approach to automatically importing the desired Component. (Currently, I'm manually importing all supported components.)
So, all credit goes to the author of html-to-react, though I may encourage him to include an example of rendering child components.

Output object name other than React with jsx syntax

with React v0.12 the #jsx pragma is gone which means it is no longer possible to output jsx with anything other than the React.METHODNAME syntax.
For my use case I am trying to wrap the React object in another object to provide some convenience methods thus, in my component files, I want to be able to write:
var myConvenienceObject = require('React-Wrapper');
var Component = myConvenienceObject.createSpecializedClass({
render: function () {
return <div />
}
})
However the jsx compiler automatially converts <div /> into React.createElement("div", null)
With older versions of React it was possible to handle this using the pragma at the top of the file. However, since that has been removed, I was wondering if there was any way currently to change the name of the object compiled by jsx so <div /> would be transformed into myConvenienceObject.createElement("div", null)
No, it's no longer possible to use a custom prefix for JSX. If you need to do this, you'll need to modify the JSX transform code, or create a fake React.
var React = require('react'), FakeReact = Object.assign({}, React, {
createElement: function(component, props, ...children){
// ...
// eventually call the real one
return React.createElement(component, props, ...children);
}
});
module.exports = FakeReact;
And then to use it you import the fake react and call it React.
var React = require('fake-react');
// ...
render: function(){ return <div />; }
If you would like to make some elements contains in your myConvenienceObject, you could consider the children props as shown in the doc. But this may need some changes in the myConvenienceObject too, to accept the children.
By the way, i'm not sure where is this createSpecializedClass functions comes from and what it does

Resources