Markdown as JSX components examples, using markdown-to-jsx
library
This is an advanced feature that is completely optional.
The ability to convert, at runtime, Markdown text into real JSX components is a game changer.
It's a very powerful feature, that should be used with care.
It allows to embed rich-content (JSX) that can be configured on external systems, such as a CMS.
The below examples use Airtable as such CMS.
Basically, from Airtable, we use some markdown within a "Long text" variable.
This variable is then fetched from NRN (using Airtable REST API).
Then, that's where the magic happens. We convert the text into JSX, using
It works based on an allowed list of JSX components. You configure yourself which HTML tag gets converted into what JSX component.
The ability to convert, at runtime, Markdown text into real JSX components is a game changer.
It's a very powerful feature, that should be used with care.
It allows to embed rich-content (JSX) that can be configured on external systems, such as a CMS.
The below examples use Airtable as such CMS.
Basically, from Airtable, we use some markdown within a "Long text" variable.
This variable is then fetched from NRN (using Airtable REST API).
Then, that's where the magic happens. We convert the text into JSX, using
markdown-to-jsx
library.It works based on an allowed list of JSX components. You configure yourself which HTML tag gets converted into what JSX component.
There are some limits at what this feature can/should do.
For instance, you can't specify
So, you can only use it with component that don't require input props.
But, those components can have an internal state, and can also depend on shared states (Redux, MobX, etc.) even though we don't show such examples here.
When using this feature, you should first ask yourself what components you want to make available from a third-party system (CMS).
You may also need to rewrite part of your existing components to take into account the above listed limitations.
Also, the concept of "separation of concerns" is very important here, you need to take special care not doing "too much" from Markdown, because you don't want to debug JSX components initialised at runtime, believe me.
Also, you probably want to have good unit/component testing to avoid undetected runtime regressions.
For instance, you can't specify
complex props
(i.e: JSX components, arrays, objects, anything that isn't scalar) of JSX components within Markdown, it's not allowed.So, you can only use it with component that don't require input props.
But, those components can have an internal state, and can also depend on shared states (Redux, MobX, etc.) even though we don't show such examples here.
When using this feature, you should first ask yourself what components you want to make available from a third-party system (CMS).
You may also need to rewrite part of your existing components to take into account the above listed limitations.
Also, the concept of "separation of concerns" is very important here, you need to take special care not doing "too much" from Markdown, because you don't want to debug JSX components initialised at runtime, believe me.
Also, you probably want to have good unit/component testing to avoid undetected runtime regressions.
Personally, I intend to use this feature to allow my customers to configure part of the site layout.
Basically, I'd build a few pre-made components that are available, and configure which ones are actually being used by each customer.
It's a great way to provide great customisation per-customer, and can also be used to work around limitations or issues.
Like any great power, use it with care, it's quite easy to make a mess of it that becomes unmaintainable, you don't want to do that.
Basically, I'd build a few pre-made components that are available, and configure which ones are actually being used by each customer.
It's a great way to provide great customisation per-customer, and can also be used to work around limitations or issues.
Like any great power, use it with care, it's quite easy to make a mess of it that becomes unmaintainable, you don't want to do that.
Configuration of our Markdown
component and allowed list of JSX components
Below is our
Markdown
component, which abstracts away the internals of markdown-to-jsx
with proper defaults for our app.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import * as Sentry from '@sentry/node';
import { createLogger } from '@/modules/core/logging/logger';
import deepmerge from 'deepmerge';
import MarkdownToJSX, { MarkdownOptions } from 'markdown-to-jsx';
import React from 'react';
import { Alert, Button, Col, DropdownItem, DropdownMenu, DropdownToggle, Nav, NavItem, NavLink, Row, UncontrolledDropdown as Dropdown } from 'reactstrap';
import { Markdown as MarkdownType } from '../../types/Markdown';
import I18nBtnChangeLocale from '../i18n/I18nBtnChangeLocale';
import I18nLink from '../i18n/I18nLink';
import Tooltip from './SimpleTooltip';
const fileLabel = 'components/utils/Markdown';
const logger = createLogger({ // eslint-disable-line no-unused-vars,@typescript-eslint/no-unused-vars
fileLabel,
});
type Props = {
text: MarkdownType;
markdownOptions?: MarkdownOptions;
}
const defaultMarkdownOptions: MarkdownOptions = {
// Make some of our own components available
overrides: { // See https://github.com/probablyup/markdown-to-jsx#optionsoverrides---override-any-html-tags-representation
// All links should open in a new tab, and ensure proper security by default
a: {
component: 'a',
props: {
rel: 'noopener', // Security, avoids external site opened through your site to have control over your site
target: '_blank',
},
},
// Reactstrap whitelisted components
Alert,
Button,
Col,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
Nav,
NavItem,
NavLink,
Row,
// Our own components
I18nLink,
I18nBtnChangeLocale,
Tooltip,
},
};
/**
* Display "text" property as Markdown, using the "markdown-to-jsx" library
*
* @param props
*/
const Markdown: React.FunctionComponent<Props> = (props): JSX.Element => {
const { text, markdownOptions: _markdownOptions } = props;
const markdownOptions = deepmerge(defaultMarkdownOptions, _markdownOptions || {});
// If providing a non-string input (like "null" or "undefined") then markdown-to-jsx will crash with "Cannot read property 'replace' of undefined" - See https://github.com/probablyup/markdown-to-jsx/issues/314
if(typeof text !== 'string'){
return null;
}
try {
return (
<MarkdownToJSX
options={markdownOptions}
>
{text}
</MarkdownToJSX>
);
} catch (e) {
// Markdown conversion might crash depending on the content, and we must absolutely avoid that
logger.error(e);
Sentry.withScope((scope): void => {
scope.setContext('props', props);
Sentry.captureException(e);
});
return (
<>
{text}
</>
);
}
};
export default Markdown;
Usage example with Tooltip
component
The Tooltip
HTML tag is mapped to SimpleTooltip
, which has been created especially for being used within Markdown, because our Tooltip
JSX component requires complex props, and thus wasn't usable as Markdown.<Tooltip text="This is a tooltip text written as text and interpreted as JSX component at runtime"><Button>Some text with a tooltip on click/hover</Button></Tooltip>
1
2
3
4
5
6
7
const markdownTooltipExample = '<Tooltip text="This is a tooltip text written as text and interpreted as JSX component at runtime"><Button>Some text with a tooltip on click/hover</Button></Tooltip>';
<Markdown
text={markdownTooltipExample}
/>
Live example with the Terms page
A more complete example, featuring more components and the ability for you to change the content through Stacker is available on the Terms page.You can use it to play around with HTML, Markdown and JSX components and see how it gets rendered.