iOS_Logo_1024x1024-100.png
  • Home
  • Examples
    Native features
    SSR (getServerSideProps)SSGSSG using fallbackSSG using revalidateCatch-all routes
    Built-in features
    HostingStages & secretsCI/CDStatic i18nMonitoringAPI (Airtable)CSS-in-JSCookies consentAnalyticsIconsCSS AnimationsUI components libraryDocs siteMarkdown as JSX
    Built-in utilities
    I18nLink componentAirtableAsset componentHooksHOCsAPIErrors handlingBundle analysisSVG to ReactSecurity auditTracking useless re-renders
  • Documentation
  • Source code
  • Go to CMS

Built-in features

  • Hosting
  • Stages & secrets
  • CI/CD
  • Static i18n
  • Monitoring
  • API (Airtable)
  • CSS-in-JS
  • Cookies consent
  • Analytics
  • Icons
  • CSS Animations
  • UI components library
  • Docs site
  • Markdown as JSX

Previous section - Next sectionHome

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

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.
iOS_Logo_1024x1024-100.png

Uber 12 - 2021
All rights reserved

Terms
Politique de confidentialité
/static/images/LOGO_Powered_by_UNLY_BLACK_BLUE.svg