By Paul Scanlon

MDX "fold it in"

  • React
  • Gatsby
  • MDX

In this post iā€™m going to discuss the term ā€œFold it inā€ which I think was first coined by spences10 and it relates to a method of using React Components in MDX without the need to import them each and every time.

This term may or may not make 100% sense but naming things is hard and having a shorter way to describe an approach or method in my experience can be quite helpful, but whatever, ā€œyou do youā€

On a recent stream with my NatterPops chums we implemented MDX in Benedicteā€™s blog and we did attempt to explain what we meant by ā€œfold it inā€ You can watch that below.

In an attempt to explain a little more iā€™ve documented a couple of ways this approach can be used.

In this example iā€™ll be specifically referring to methods I use in my Gatsby builds when working with MDX

MDX

For those not familiar with MDX itā€™s similar to Markdown and additionally provides a method to render React components along with typical Markdown syntaxā€¦ MDX is brilliant and I love it!

Hereā€™s an example šŸ‘‡

// some-blog-post.mdx

import CustomBlockquote from '../src/components/custom-blockquote'

## This is a heading written in markdown

This is the body written in markdown

And this is a React component šŸ‘‡

<CustomBlockquote>This is a quote - from someone</CustomBlockquote>

This will result in something like the below šŸ‘‡

This is a heading written in markdown

This is the body written in markdown

And this is a React component šŸ‘‡

<CustomBlockquote>This is a quote - from someone</CustomBlockquote>

Youā€™ll see near the top of some-blog-post.mdx thereā€™s a familiar looking import statement, this is pretty much what youā€™d expect to see if you were in Jsx land.

The import statement works the same way in MDX and allows you to import React components and render them alongside the usual Markdown syntax, but because itā€™s a React component you can be a little more fancy. In this example iā€™ve added x2 SVG quote icons either side of the text.

This approach works great for ā€œone offā€ components but in the case of the <CustomBlockquote /> you might want to use it more regularly when writing blog posts and having to import it for each and every MDX file can be a bit of a faff.

Fold it in

Itā€™s at this point where you might like to think about providing all MDX files with the ability to render the <CustomBlockquote /> component without needing to import it first. Itā€™s here where the term ā€œfold it inā€ makes a bit more sense.

By ā€œfoldingā€ the component in to the <MDXProvider /> it will be ready to use by any MDX file without the need to import it first.

Your implementation of MDX will likely be different to mine but you will probably have an <MDXProvider /> somewhere in your project. Hereā€™s a stripped back MDX Template file.

// src/pages/{mdx.slug}.js

import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { MDXProvider } from '@mdx-js/react'

const MdxTemplate = ({
  data: {
    mdx: {
      body,
    },
  },
}) => {
  return (
    ...
    <MDXProvider>
      <MDXRenderer>{body}</MDXRenderer>
    </MDXProvider>
    ...
  )
}

export const query = graphql`
  query($id: String) {
    mdx(id: { eq: $id }) {
      body
    }
  }
`;

export default MdxTemplate

MDXProvider

To ā€œfoldā€ components into MDX I use the components prop on the <MDXProvider /> and pass in the components iā€™d like to make available to all MDX files.

// src/pages/{mdx.slug}.js

import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { MDXProvider } from '@mdx-js/react'

+ import CustomBlockquote from '../../components/custom-blockquote'

+ const mdxComponents = {
+  CustomBlockquote
+ }

const MdxTemplate = ({
  data: {
    mdx: {
      body,
    },
  },
}) => {
  return (
    ...
-    <MDXProvider>
+    <MDXProvider components={mdxComponents}>
      <MDXRenderer>{body}</MDXRenderer>
    </MDXProvider>
    ...
  )
}

export const query = graphql`
  query($id: String) {
    mdx(id: { eq: $id }) {
      body
    }
  }
`;

export default MdxTemplate

Now that the <CustomBlockquote /> component has been ā€œfoldedā€ in thereā€™s no need to import it in the MDX blog post.

// some-blog-post.mdx

- import CustomBlockquote from '../src/components/custom-blockquote'

 ## This is a heading written in markdown

 This is the body written in markdown

 And this is a React component šŸ‘‡

 <CustomBlockquote>This is a quote - from someone</CustomBlockquote>

Iā€™ve been asked a number of times about how this might affect bundle size / page size because using this method bundles the <CustomBlockquote /> component with each page regardless of if itā€™s being used or not.

I have to be honest I donā€™t know if thatā€™s actually the case. My assumption would be that webpack is smart enough to know if a component is in use or not and therefore would only bundle the <CustomBlockquote /> as and when itā€™s required but thatā€™s all a bit low level for me.

If you have questions surrounding the potential performance impacts of using this approach you might like to ask either Chris Biscardi or John Otander, theyā€™re both very approachable chaps and were heavily involved in the creation of MDX.

However, if you have any other questions iā€™ll do my best to answer them!

MDXRenderer

In the above example iā€™m using the <MDXProvider /> from @mdx-js/react and passing in a React component, in this next bit iā€™m going to use the <MDXRenderer /> from gatsby-plugin-mdx to fold in ā€œdataā€.

Iā€™ll use the <CustomBlockquote /> component again but this time rather than rendering itā€™s children iā€™m going to pass data stored in frontmatter back through the <MDXRenderer /> and make it available as a prop in the MDX body šŸ„“

Frontmatter

First I add a new field in frontmatter called quotes, itā€™s of type array but looks like a list syntax in Markdown. If the below diff is a little hard to read, the quotes field should look like this, the --- are important as they signify the beginning and end of frontmatter

---
 quotes:
   - 'this is a quote 1 - from someone a'
   - 'this is a quote 2 - from someone b'
   - 'this is a quote 3 - from someone c'
---

Props instead of children

This is a weird one, but notice now instead of rendering the children of <CustomBlockquote /> as text Iā€™m using an escaped Jsx syntax and pointing it to props.quotes[0]

The [0] is normal array syntax and represents an index from an array

// some-blog-post.mdx

+ ---
+ quotes:
+   - 'this is a quote 1 - from someone a'
+   - 'this is a quote 2 - from someone b'
+   - 'this is a quote 3 - from someone c'
+ ---


 ## This is a heading written in markdown

 This is the body written in markdown

 And this is a React component šŸ‘‡

- <CustomBlockquote>This is a quote - from someone</CustomBlockquote>
+ <CustomBlockquote>{props.quotes[0]}</CustomBlockquote>
+ <CustomBlockquote>{props.quotes[1]}</CustomBlockquote>
+ <CustomBlockquote>{props.quotes[2]}</CustomBlockquote>

MDXRenderer Props

In order for props.quotes[0] to equal something other than null I now query the frontmatter from the MDX Template file and pass the quotes back to the <MDXRenderer /> on a prop iā€™ve also called quotes


// src/pages/{mdx.slug}.js

import React from 'react'
import { graphql } from 'gatsby'
import { MDXRenderer } from 'gatsby-plugin-mdx'
import { MDXProvider } from '@mdx-js/react'

import CustomBlockquote from '../../components/custom-blockquote'

const mdxComponents = {
  CustomBlockquote
}

const MdxTemplate = ({
  data: {
    mdx: {
+     quotes,
      body,
    },
  },
}) => {
  return (
    ...
    <MDXProvider components={mdxComponents}>
-     <MDXRenderer>{body}</MDXRenderer>
+     <MDXRenderer quotes={quotes}>{body}</MDXRenderer>
    </MDXProvider>
    ...
  )
}

export const query = graphql`
  query($id: String) {
    mdx(id: { eq: $id }) {
+     frontmatter {
+       quotes
      }
      body
    }
  }
`;

export default MdxTemplate

This is a slightly contrived example but I suppose it might be useful if you had a really long blog post with lots of quotes and rather than having to scroll through the page and find one that might need editing you could find the quote in question by looking at the top of the file in the frontmatter. šŸ¤·ā€ā™‚ļø

A more ā€œReal Worldā€ example of how the <MDXRenderer /> can be used to pass data from frontmatter back to the MDX body can be see in this rather outdated post: MDX Embedded Images.

In this post I pass local image files from frontmatter, process them with childImageSharp in the MDX Template before passing them back to the <MDXRenderer /> to display them anywhere in the MDX body.

Phewā€¦ that just about wraps things upā€¦ see you around šŸ•ŗ

Hey!

Leave a reaction and let me know how I'm doing.

  • 0
  • 0
  • 0
  • 0
  • 0
Powered byNeon