A lightweight library that converts raw HTML to a React DOM structure.
Clone or download
Pull request Compare This branch is 193 commits ahead of mikenikles:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

html-to-react

Greenkeeper badge Build Status npm version Dependency Status Coverage Status npm

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 HtmlToReact = require('html-to-react');
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 HtmlToReact = require('html-to-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;
};

var processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React);

// 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