70 %
Chris Biscardi

Using MDX shortcodes in react-live scope

Importing a component into an MDX file provides it to the MDX content in a different way than using MDXProvider context. Let's say Button is a shortcode instead of an import and thus coming from an MDXProvider. This means it's not being imported anywhere and as a result is not available in useMDXScope from gatsby-plugin-mdx. Note the following MDX which is the same as the useMDXScope post, except we're using Button as a shortcode so we don't use the import.

md
# Something
<Button text="Whatever"/>
describing some stuff
```js react-live
<Button text="whatever" />
```
and saying some more for the docs

Shortcodes are implemented by using an MDXProvider to provide React components as such:

JS
<MDXProvider
components={{
Button: props => <button>my button</button>
}}
>
{app}
</MDXProvider>

So let's get set up here. Using the same template in gatsby-browser.js as our last post, we've made a few adjustments.

JS
import React from 'react'
import { MDXProvider } from '@mdx-js/react'
import Highlight, { defaultProps } from 'prism-react-renderer'
import { LiveProvider, LiveEditor, LiveError, LivePreview } from 'react-live'
import { preToCodeBlock } from 'mdx-utils'
// import {useMDXScope} from 'gatsby-plugin-mdx/context'
import { useMDXComponents } from '@mdx-js/react'
const Code = ({ codeString, language, ...props }) => {
const components = useMDXComponents()
if (props['react-live']) {
return (
<LiveProvider
code={codeString}
scope={components}>
<LiveEditor />
<LiveError />
<LivePreview />
</LiveProvider>
)
} else {
return (
<Highlight {...defaultProps} code={codeString} language={language}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
<pre className={className} style={style}>
{tokens.map((line, i) => (
<div {...getLineProps({ line, key: i })}>
{line.map((token, key) => (
<span {...getTokenProps({ token, key })} />
))}
</div>
))}
</pre>
)}
</Highlight>
)
}
}
// components is its own object outside of render so that the references to
// components are stable
const components = {
Button: props => Button: props => <button>my button</button>,
pre: preProps => {
const props = preToCodeBlock(preProps)
// if there's a codeString and some props, we passed the test
if (props) {
return <Code {...props} />
} else {
// it's possible to have a pre without a code in it
return <pre {...preProps} />
}
},
}
export const wrapRootElement = ({element}) => {
return <MDXProvider components={components}>{element}</MDXProvider>
}

The important chage here is the import from @mdx-js/react.

JS
import { useMDXComponents } from "@mdx-js/react";

Which we then use to replace our usage of useMDXScope. (note: it's perfectly fine to keep both hooks and merge them together, we'll talk about this in the finale of this series of posts)

JS
const components = useMDXComponents();

Finally, since we said we're using shortcodes, we need a shortcode to use. In this case we chose to use Button, so we'll hard-code a button that shows the text my button.

JS
const components = {
Button: props => Button: props => <button>my button</button>,
pre: preProps => {
const props = preToCodeBlock(preProps)
// if there's a codeString and some props, we passed the test
if (props) {
return <Code {...props} />
} else {
// it's possible to have a pre without a code in it
return <pre {...preProps} />
}
},
}

This results in our markdown code blocks being able to use components that were provided as shortcodes, potentially making documentation easier.