dom-to-react
is a replacement for React's own method dangerouslySetInnerHTML
.
It lets you build a React structure from a regular HTML-Dom. Your React application is aware of all elements wthin the added DOM, and it even lets you initialize your own React components anywhere inside that structure.
dom-to-react is very lightweight. It does not use any further third party modules.
Use npm (or your preferred package manager)
npm install --save-dev dom-to-react
It takes a regular Dom-Node as entrypoint from which it creates the according React-elements (by calling React.createElement()
) recursively. So the simplest way is:
<main id="app">
<div class="content-to-render">
<p>A Paragraph</p>
</div>
</main>
import Dom2react from 'dom-to-react';
import React from 'react';
import { render } from 'react-dom';
const d2r = new Dom2react();
const rootNode = document.querySelector('.content-to-render');
render(d2r.prepareNode(rootNode), document.querySelector('#app'));
This will create the initial DOM structure, but now all elements are React elements. Easy, isn't it?
when creating an instance of Dom2React, a configuration can be provided which allows to manipulate and handle the original DOM:
const d2r = new Dom2react(options);
where options
is an array with instruction objects with each 2-3 callback functions
all the functions are being passed the following params:
@param {node}
the node being tested/manipulated@param {key}
the React-key which would be assigned when the node renders (always in the format${level}-${index}
)@param {level}
the level how deep in the DOM the nod is nested (an integer)@param {parser}
the parser itself
var options = [
{
// If this function returns true, the two following functions are called as long as they are defined
// This function must always return true or false
'condition': function(node, key) { return node.nodeName.toLowerCase() === 'div'; },
// This function can be used for easy manipulations to the node, e.g. for removing or adding attributes
// This function must always return a DOM-Node (even if it's a new one created by document.createElement)
'modify': function(node, key, level) { return document.createElement('div'); },
//This function is used to inject your own components into the parsed DOM
// This function must return an object which can be rendered by React (a react element or null)
'action': function(node, key, level) { return React.createElement('div'); }
}
];
{
condition: function(node, key, level, parser) { return node.nodeName.toLowerCase() === 'div';} ),
modify: function(node, key, level) {
node.className += ' a-class-added';
return node;
}
}
{
condition: function(node, key, level parser) { return node.className.indexOf('delete-me') >= 0;} ),
action: function(node, key, level, parser) {
return null;
}
}
Initialize a react component for all nodes of a certain type (e.g. the react-markdown-component):
{
condition: function(node, key, level, parser) return {node.nodeName.toLowerCase() === 'pre'},
action: function(node, key, level, parser) {
return <ReactMarkdown key={key} source={node.innerText} />;
}
}
{
condition: function(node, key, level parser) { return node.nodeName.toLowerCase() === 'ul'},
modify: function(node, key, level, parser) {
var ol = document.createElement('ol');
for (var i = node.childNodes.length - 1; i >= 0; i--) {
ol.appendChild(node.childNodes[i]);
}
return ol;
}
}
Initialize a more complex component with an object parsed from a JSON within a HTML comment. (That's actually what I used this for đź‘Ť)
<div class="complex-component">
<!-- { ...complex JSON-object } -->
</div>
{
condition: function(node, key, level, parser) { return node.className.indexOf('complex-component') >= 0;},
action: function(node, key, level, parser) {
var props = false;
for (var i = node.childNodes.length - 1; i >= 0; i--) {
if (childNode.nodeType === 8) {
props = JSON.parse(childNode.nodeValue);
}
}
return <ComplexComponent {...props} />;
}
}
dom-to-react is not meant to directly load all instructions from a node's style
-attribute.
But as suggested here, style instructions can be added if required as follows:
<div class="with-style" style="background-color: rebeccapurple; color: lime; border-radius: 7px; padding: 12px;">
This Element has a <code>style</code> attribute.<br>
It also shows how to use the <code>parser</code>-argument
</div>
{
action: function(node, key, level, parser) {
// example how to adapt styling instructions from a node
const hyphen2CamelCase = (str) => str.replace(/-([a-z])/gi,(s, group) => group.toUpperCase());
const style2object = (styleString) => styleString.split(';').filter(s => s.length).reduce((styles, statement) => {
const keyValue = statement.split(':');
styles[hyphen2CamelCase(keyValue[0]).trim()] = keyValue[1].trim();
return styles;
}, {});
return <div key={key} style={style2object(node.getAttribute('style'))}>{
// the parser can be called again to handle "regular" child-nodes within a component
parser.prepareChildren(node.childNodes, level)
}</div>;
}
To see the included demo in action, clone/download this repo and
npm i
npm run start
and open
http://localhost:8080/