Skip to main content

Reinvent the Wheel

To understand how this library works, we propose a teeny, tiny implementation of an HTML renderer in just about 40 lines of code. Of course, it has many limitations that are overcomed by react-native-render-html, but it will give you a good glimpse at how things work internally.

Implementation#

To do so, we will need an HTML parsing library which will give us some sort of proxy DOM representation of the HTML source. In this very example, we will use โ€‹htmlparser2 libarary:

RenderHtml.jsx
import {Text, View} from 'react-native';
import {parseDocument, ElementType} from 'htmlparser2';
import React, {PureComponent} from 'react';
export default class RenderHtml extends PureComponent {
ignoredTags = ['head'];
textTags = ['span', 'strong', 'em'];
renderTextNode(textNode, index) {
return <Text key={index}>{textNode.data}</Text>;
}
renderElement(element, index) {
if (this.ignoredTags.indexOf(element.name) > -1) {
return null;
}
const Wrapper = this.textTags.indexOf(element.name) > -1 ? Text : View;
return (
<Wrapper key={index}>
{element.children.map((c, i) => this.renderNode(c, i))}
</Wrapper>
);
}
renderNode(node, index) {
switch (node.type) {
case ElementType.Text:
return this.renderTextNode(node, index);
case ElementType.Tag:
return this.renderElement(node, index);
}
return null;
}
render() {
const document = parseDocument(this.props.html);
return document.children.map((c, i) => this.renderNode(c, i));
}
}

Below is an overview of the component's render method invocation:

  1. Line 36 invokes parseDocument from โ€‹htmlparser2 which returns the root DOM node of the document.

  2. Line 37 returns the mapping of the root's children with the result of renderNode method.

  3. Line 25, the renderNode method returns: the result of renderTextNode when provided with a DOM Text node, the result of renderElement when the provided node is an Element, and null otherwise, such as when the provided node is a comment, script, or stylesheet.

Although the renderTextNode implementation is pretty straightforward,renderElement has some conditional logic to render the element either in a React Native โ€‹Text or โ€‹View. This is to bypass rendering glitches when embedding โ€‹View inside โ€‹Text, such as discussed in more details in the below section (hoisting).

note

We allude to the DOM an DOM nodes while โ€‹htmlparser2 only provides a substet of the DOM API for lightweightness!

Discussion#

Perhaps your requirements are so simple that this might actually be sufficient for your use-case. You could try to extend this naive implementation with the below, easy to implement features:

  1. Add custom renderers for specific tags such as โ€‹<img>, โ€‹<ul>...

  2. Add styles for specific tags and classes.

However, you will get involved in a much substantial and complex task if you have requirements such as:

  1. Support inline styles. You would need to transform those styles into React Native compatible styles. Beware that unsupported styles on the native side could easily crash your app.

  2. Support whitespace collapsing such as in โ€‹white-space CSS property.

  3. Support URL resolutions, such as relative URLs, โ€‹<base> elements... etc.

  4. Support โ€‹hoisting. Because React Native โ€‹View elements are not well handled inside โ€‹Text elements, these should be hoisted up in the tree to be rendered inside Views.

  5. Support complete CSS inheritance. For example, a โ€‹<div> element could have a style with text properties such as โ€‹color, but a React Native โ€‹View element which is the default mapping for โ€‹<div> will not support such style property. See โ€‹CSS Processing page.

react-native-render-html overcomes all of those caveats and more out of the box!