Skip to content

Instantly share code, notes, and snippets.

@johno
Created April 16, 2018 21:15
Show Gist options
  • Save johno/77a7f142b847ed130f767eadb7f6b790 to your computer and use it in GitHub Desktop.
Save johno/77a7f142b847ed130f767eadb7f6b790 to your computer and use it in GitHub Desktop.
Dynamic MDX rendering
import React from 'react'
import Markdown from './Markdown'
const components = {
h1: props => <h1 style={{ color: 'tomato' }} {...props} />
}
const scope = {
name: 'world!',
Box: props => <div style={{border: 'thin solid tomato'}} {...props} />
}
const mdx = `
# Hello, <span>{name}</span>
<Box>
<h4>!!!!!</h4>
</Box>
`
export default () => (
<Markdown
components={components}
children={mdx}
{...scope}
/>
)
import React, { Component } from 'react'
import mdx from '@mdx-js/mdx'
import { MDXTag } from '@mdx-js/tag'
import * as babel from 'babel-standalone'
const parse = raw => babel.transform(raw, {
plugins: [
require('babel-plugin-transform-react-jsx')
],
presets: [
'react',
'stage-0'
]
}).code
export default class Markdown extends Component {
state = {
Component: null
}
mdxify = async () => {
const { children, components, ...props } = this.props
const scope = { components, ...props, MDXTag }
const jsx = await mdx(children, { components })
// Hacky workaround
const code = jsx.replace(/^(\s)*export default/, '')
const keys = Object.keys(scope)
const values = keys.map(k => scope[k])
const fn = new Function('React', ...keys, `return ${parse(code)}`)
const Component = fn(React, ...values)
this.setState({ Component })
}
// Should prolly recreate on props change
componentWillMount () {
this.mdxify()
}
render () {
const { Component } = this.state
return Component ? <Component /> : null
}
}
@david-mart
Copy link

for anyone who comes across this, this is the current way of rendering string markdown to react

import { KModules } from '..'; // object with my custom components
import { EvaluateOptions, evaluateSync } from '@mdx-js/mdx';
import { MDXProvider, useMDXComponents } from '@mdx-js/react';
import { FC, useMemo } from 'react';
import * as runtime from 'react/jsx-runtime';

export interface KomponentProps {
  children?: React.ReactNode;

  /**
   * Markdown string to be rendered. If this is set, `children` will be ignored.
   */
  markdown?: string;

  /**
   * Replace the default components with your own.
   */
  components?: Parameters<typeof MDXProvider>[0]['components'];

  /**
   * Add extra components to the default ones.
   */
  extra?: Parameters<typeof MDXProvider>[0]['components'];
}

export const Komponent: FC<KomponentProps> = ({
  children,
  markdown,
  components,
  extra,
}) => {
  const modules =
    components ??
    Object.entries(KModules).reduce(
      (ms, [key, value]) => ({ ...ms, [key]: value }),
      extra,
    );

  const MDXContent = useMemo(() => {
    try {
      return markdown
        ? evaluateSync(markdown, {
            ...runtime,
            useMDXComponents,
          } as EvaluateOptions).default
        : null;
    } catch (e) {
      return () => (e as Error).message;
    }
  }, [markdown]);

  return (
    <MDXProvider components={modules}>
      {MDXContent ? <MDXContent /> : children}
    </MDXProvider>
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment