A lightweight library that converts raw HTML to a React DOM structure.
Why?
I had a scenario where an HTML template was generated by a different team, yet I wanted to leverage React for the parts I did have control over. The template basically contains something like:
<div class="row">
<div class="col-sm-6">
<div data-report-id="report-1">
<!-- A React component for report-1 -->
</div>
</div>
<div class="col-sm-6">
<div data-report-id="report-2">
<!-- A React component for report-2 -->
</div>
</div>
</div>
I had to replace each <div> that contains a data-report-id attribute with an actual report,
which was nothing more than a React component.
Simply replacing the <div> elements with a React component would end up with multiple top-level
React components that have no common parent.
The html-to-react module solves this problem by parsing each DOM element and converting it to a React tree with one single parent.
Installation
$ npm install --save html-to-react
Examples
Simple
The following example parses each node and its attributes and returns a tree of React elements.
var ReactDOMServer = require('react-dom/server'); var HtmlToReactParser = require('html-to-react').Parser; var htmlInput = '<div><h1>Title</h1><p>A paragraph</p></div>'; var htmlToReactParser = new HtmlToReactParser(); var reactElement = htmlToReactParser.parse(htmlInput); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement); assert.equal(reactHtml, htmlInput); // true
With Custom Processing Instructions
If certain DOM nodes require specific processing, for example if you want to capitalize each
<h1> tag, the following example demonstrates this:
var ReactDOMServer = require('react-dom/server'); var HtmlToReactParser = require('html-to-react').Parser; var htmlInput = '<div><h1>Title</h1><p>Paragraph</p><h1>Another title</h1></div>'; var htmlExpected = '<div><h1>TITLE</h1><p>Paragraph</p><h1>ANOTHER TITLE</h1></div>'; var isValidNode = function() { return true; }; // Order matters. Instructions are processed in the order they're defined var processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React); var processingInstructions = [ { // Custom <h1> processing shouldProcessNode: function(node) { return node.parent && node.parent.name && node.parent.name === 'h1'; }, processNode: function(node, children) { return node.data.toUpperCase(); } }, { // Anything else shouldProcessNode: function(node) { return true; }, processNode: processNodeDefinitions.processDefaultNode }]; var htmlToReactParser = new HtmlToReactParser(); var reactComponent = htmlToReactParser.parseWithInstructions(htmlInput, isValidNode, processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup(reactComponent); assert.equal(reactHtml, htmlExpected);
Replace the Children of an Element
There may be a situation where you want to replace the children of an element with a React component. This is beneficial if you want to:
- a) Preserve the containing element
- b) Not rely on any child node to insert your React component
Example
Below is a simple template that could get loaded via ajax into your application
Before
<div class="row">
<div class="col-sm-6">
<div data-container="wysiwyg">
<h1>Sample Heading</h1>
<p>Sample Text</p>
</div>
</div>
</div>
After
You may want to extract the inner html from the data-container attribute, store it and then pass
it as a prop to your injected RichTextEditor.
<div class="row">
<div class="col-sm-6">
<div data-container="wysiwyg">
<RichTextEditor html={"<h1>Sample heading</h1><p>Sample Text</p>"} />
</div>
</div>
</div>
Setup
In your instructions object, you must specify replaceChildren: true.
var React = require('react'); var HtmlToReactParser = require('html-to-react').Parser; var htmlToReactParser = new HtmlToReactParser(); var htmlInput = '<div><div data-test="foo"><p>Text</p><p>Text</p></div></div>'; var htmlExpected = '<div><div data-test="foo"><h1>Heading</h1></div></div>'; var isValidNode = function() { return true; }; // Order matters. Instructions are processed in // the order they're defined var processingInstructions = [ { // This is REQUIRED, it tells the parser // that we want to insert our React // component as a child replaceChildren: true, shouldProcessNode: function (node) { return node.attribs && node.attribs['data-test'] === 'foo'; }, processNode: function (node, children, index) { return React.createElement('h1', {key: index,}, 'Heading'); } }, { // Anything else shouldProcessNode: function (node) { return true; }, processNode: processNodeDefinitions.processDefaultNode, }, ]; var reactComponent = parser.parseWithInstructions( htmlInput, isValidNode, processingInstructions); var reactHtml = ReactDOMServer.renderToStaticMarkup( reactComponent); assert.equal(reactHtml, htmlExpected);
Tests & Coverage
Test locally: $ npm test
Test with coverage and report coverage to Coveralls: $ npm run test-coverage
Test with coverage and open HTML report: $ npm run test-html-coverage