I started writing this as a result of a conversation we had on Jason's Livestream about how to get code blocks to use the components imported in the same file. The reference in use was one of my own blog posts Codeblocks, MDX, and mdx-utils that described the motivation behind publishing the original mdx-utils library.
That conversation reminded me of something John Otander told me about what some people prefer as the default behavior for MDX when using react-live.
Which leads me to the problem: How do we get Button
to be
used in this code block.
import Button from '../components/button'# Something<Button text="Whatever"/>describing some stuff```js live<Button text="whatever" />```and saying some more for the docs
Well lets first take all of the code from the last blog post
and dump it into gatsby-browser.js
. This gives a concise
overview of what we're working with.
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";const Code = ({ codeString, language, ...props }) => {if (props["react-live"]) {return (<LiveProvider code={codeString}><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 stableconst components = {pre: preProps => {const props = preToCodeBlock(preProps);// if there's a codeString and some props, we passed the testif (props) {return <Code {...props} />;} else {// it's possible to have a pre without a code in itreturn <pre {...preProps} />;}}};export const wrapRootElement = ({ element }) => {return (<MDXProvider components={components}>{element}</MDXProvider>);};
A markdown codeblock gets syntax highlighted by
prism-react-renderer
by default, and if the codeblock uses the react-live
metastring prop, we render with
react-live
instead.
We do have a problem here though. The above code all put together in our Gatsby site still results in an error in our react-live output.
ReferenceError: Button is not defined
To fix this we have a couple of lines of code to add. First
is a hook import from gatsby-plugin-mdx
that we can use to
grab the MDX components in scope.
import { useMDXScope } from "gatsby-plugin-mdx/context";
The second is to use that hook and the resulting components
list in the scope of our LiveProvider
.
const Code = ({ codeString, language, ...props }) => {+ const components = useMDXScope()if (props['react-live']) {return (<LiveProvidercode={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>)}}
This allows us to use our custom components inside of our react-live playgrounds.