Skip to main content

Custom Rendering

The renderer API shipped since Foundry (v6) is at the same time more strict and more flexible. To get ready for this new API, you must understand some basics of the transient render tree produced by the ​TRE:

  1. During the transient render tree generation, every DOM node is translated to a ​TNode.

  2. ​TText nodes correspond to DOM ​Text nodes (anonymous ​TText nodes) or DOM elements which children are DOM ​Text nodes (named ​TText nodes). So a ​TText node cannot have children, and its content is a string accessible with the ​data field.

  3. Thanks to hoisting, ​TPhrasing nodes can only have ​TText and ​TPhrasing nodes as children.

  4. ​TBlock nodes can have any children.

tip

You are kindly advised to read ​Transient Render Engine page before continuing!

Options for Custom Rendering#

You can customize rendering at two steps of the flow:

  1. During ​TRT generation. via HTML model definition.

  2. At (React) render time via custom renderers.

These customizations are not exclusive, so you can use both approaches at the same time.

Model-based Custom Rendering#

Example: Registering a New Tag#

Let's say we have defined an advanced, powerful <blue-circle> Web Component for our website and we need to register a custom renderer for this tag. If we don't, the <blue-circle> elements will be translated to ​TEmpty and won't be rendered.

important

We must register an element model for this tag because it is non-standard.

important

Custom tags in HTML markup must never be self-closing. The HTML parser will not recognize non-void self-closing tags by default which will lead to unexpected outcomes.

tip

We may register a custom component renderer, but this is not mandatory (see next chapter).

A Custom Tag Example
import React from 'react';
import { useWindowDimensions } from 'react-native';
import RenderHtml, { HTMLElementModel, HTMLContentModel } from 'react-native-render-html';
const source = {
html: '<blue-circle></blue-circle>'
};
const customHTMLElementModels = {
'blue-circle': HTMLElementModel.fromCustomModel({
tagName: 'blue-circle',
mixedUAStyles: {
width: 50,
height: 50,
borderRadius: 25,
alignSelf: 'center',
backgroundColor: 'blue'
},
contentModel: HTMLContentModel.block
})
};
export default function App() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
customHTMLElementModels={customHTMLElementModels}
/>
);
}
A Custom Tag rendered by defining an element model for this tag via HTMLElementModel.fromCustomModel.
Press on the button below to show how this code renders on your device.

Example: Displaying Inline Images#

In the below example, we are changing the element model of the​<img> tag to support inline rendering. For this purpose, we take advantage of the ​customHTMLElementModels prop!

Customizing Content Model
import React from 'react';
import { useWindowDimensions } from 'react-native';
import RenderHtml, { HTMLContentModel, defaultHTMLElementModels } from 'react-native-render-html';
const source = {
html: `<p style="text-align:center">
Those are inline images!<br/><br/>
<strong>before</strong>
<img src="https://www.fillmurray.com/50/50" width="50" height="50" />&nbsp;
<img src="https://www.fillmurray.com/70/50" width="70" height="50" />&nbsp;
<strong>after</strong>
</p>`
};
const customHTMLElementModels = {
img: defaultHTMLElementModels.img.extend({
contentModel: HTMLContentModel.mixed
})
};
export default function App() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
customHTMLElementModels={customHTMLElementModels}
/>
);
}
A usage of customHTMLElementModels prop to change the content model of <img> tags thanks to "extend" method of HTMLElementModel.
Press on the button below to show how this code renders on your device.
note

We used ​extend method to change the content model for the ​<img> tag. This method is immutable: it creates a new ​HTMLElementModel instance. It is thus safe to use this method to create models for new tags derived from models of existing tags.

warning

You cannot set the contentModel dynamically. This is currently a limitation of the ​TRE.

Component-based Custom Rendering#

Minimal Example#

You can register custom renderers components with the ​renderers prop.

Stop talking, let's try it out. We're going to define a renderer for the ​<h1> element which supports press interactions:

A Custom Renderer Making H1 Interactive
import React from 'react';
import RenderHtml from 'react-native-render-html';
import { useWindowDimensions, Alert } from 'react-native';
function H1Renderer({
TDefaultRenderer,
...props
}) {
const onPress = () => Alert.alert('pressed!');
return (
<TDefaultRenderer
{...props}
onPress={onPress}
/>
);
}
const tagsStyles = {
article: {
marginHorizontal: 10
}
};
const source = {
html: `
<article>
<h1>Press me!</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam.
</p>
</article>
`
};
const renderers = {
h1: H1Renderer
};
export default function App() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
tagsStyles={tagsStyles}
source={source}
renderers={renderers}
/>
);
}
A custom renderer taking advantage of onPress prop available in TDefaultRenderer passed component.
Press on the button below to show how this code renders on your device.
tip

The wrapper component injected when handling onPress for ​TBlock nodes is defined by the ​GenericPressable prop. You can also customize the highlight color with ​pressableHightlightColor. Also note that onPress works with textual nodes, in which case the eponym prop of React Native ​Text element will be used instead.

tip

​TDefaultRenderer can receive textProps prop which will be used when rendering a React Native ​Text element, and viewProps for ​View elements.

Children Tampering#

Let's continue with a common requirement: injecting ads in the body of an article. More precisely, after the 2d and 4th children. To achieve our goal, we are going to use the ​TChildrenRenderer component:

Inserting Elements at Render Time
import React from 'react';
import { useWindowDimensions, View, Text } from 'react-native';
import RenderHtml, { TChildrenRenderer } from 'react-native-render-html';
function AdComponent() {
return (
<View
style={{ backgroundColor: 'purple', padding: 10, alignSelf: 'stretch' }}>
<Text style={{ color: 'white' }}>πŸ˜ˆπŸ€‘πŸ˜ˆπŸ€‘πŸ˜ˆπŸ€‘</Text>
</View>
);
}
function ArticleWithAds({
TDefaultRenderer,
tnode,
...defaultRendererProps
}) {
const firstChildrenChunk = tnode.children.slice(0, 2);
const secondChildrenChunk = tnode.children.slice(2, 4);
const thirdChildrenChunk = tnode.children.slice(4);
return (
<TDefaultRenderer tnode={tnode} {...defaultRendererProps}>
<TChildrenRenderer tchildren={firstChildrenChunk} />
{firstChildrenChunk.length === 2 ? <AdComponent /> : null}
<TChildrenRenderer tchildren={secondChildrenChunk} />
{secondChildrenChunk.length === 2 ? <AdComponent /> : null}
<TChildrenRenderer tchildren={thirdChildrenChunk} />
</TDefaultRenderer>
);
}
const source = {
html: `<article>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<p>Paragraph 3</p>
<p>Paragraph 4</p>
<p>Paragraph 5</p>
</article>`
};
const tagsStyles = {
article: {
marginHorizontal: 10
}
};
const renderers = {
article: ArticleWithAds
};
export default function App() {
const { width } = useWindowDimensions();
return (
<RenderHtml
contentWidth={width}
source={source}
tagsStyles={tagsStyles}
renderers={renderers}
/>
);
}
A custom renderer taking advantage of "TChildrenRenderer" component. When "TDefaultRenderer" is given children, those will replace the default children rendering logic, allowing great customizability.
Press on the button below to show how this code renders on your device.

The foundry API is powerful in terms of rendering customization. It is very easy to insert child elements, while preserving the internal rendering logic.

tip

​TChildrenRenderer can receive a ​renderChild prop to customize the rendering logic for each child.

Renderer Props Summary#

A custom renderer will receive the below props:

​tnode

The ​TNode to render.

​TDefaultRenderer

The default fallback renderer for this ​TNode.

​InternalRenderer

The internal renderer for this tagName. An internal renderer is like a custom renderer, but registered internally. If there is no internal renderer registered for this tag, ​InternalRenderer will be equal to ​TDefaultRenderer.

​style

The flatten style object which should be passed to the root element returned by this component.

​textProps

To use when you render a ​Text-based element (e.g. the ​type prop is "text").

​viewProps

To use when you render a ​View-based element (e.g. the ​type prop is "block").

​type

To check whether a ​Text ("text") or ​View ("block") is expected as the root element returned by this component.

​propsFromParent

Props passed directly from the parent custom renderer via ​TChildrenRenderer. See ​propsForChildren prop.

​renderIndex

The position relative to the parent React element, starting at 0. Not to be confused with ​TNodeShape.index the position of the ​TNode before hoisting, which is much closer to the position in the DOM.

​renderLength

The total number of children of this React element parent. e.g. number of React element siblings + 1.

See ​CustomRendererProps for a complete reference.