tests

Code Wrapper

I'm going to make a few assumptions here...

  • You know what React, MDX, Javascript, Node, Gatsby, and SASS are.
  • You have a basic understanding of how one builds a web page, not necessarily a React or Node application, but general html/js/css.

If any of these make you scratch your head, I suggest that you go read about each of them and familiarize yourself before reading any further.

Wrapping your site

There are other site generators out there (that have similar functionality and will allow you to wrap your site/application), but since I'm using Gatsby here, that's what we'll be focusing on for the time being.

If you don't already have one, create a file called gatsby-browser.js in the root of your project. Next, create a file called root_wrapper.js somewhere in your project as well. I put mine in /src/components/global/, since that's where I like to store my global component files (go figure). You can put this wherever you want, as long as it's in the /src/ directory somewhere.

Inside of gatsby-browser.js, you'll want to import the component that you just created, as well as exporting the constant, so the element(s) that we're going to wrap will be available in other files. While we could go ahead and setup rules for our wrapper elements directly inside of gatsby-browser.js, I prefer to have things a bit more component-ized.

While I'm here, I'll go ahead and add my main.scss file. This sources my base scss styles for the entire site.

import { wrapRootElement as wrap } from './src/components/global/root_wrapper';

require('./src/styles/main.scss'); // Loads the main scss file

export const wrapRootElement = wrap;

If you're familiar with how css can transform on object by adding colors, backgrounds, margins, etc – our wrapper file is going to allow us to do something similar. In this case, we're going to be transforming the whole object, not just a single object style, like css. And while you can make css rules that are both simple, or extremely complex – you can do the same thing here. It's up to you how much you want to customize things in your wrapper.

For instance, if I want all of the headers on my site that use a h1 tag to be red, I could do the following in a stylesheet:

h1 { color: red; }

or, in an jsx file – with inline styles:

<h1 style={{color: "red"}}>My Headline</h1>

or, in a jsx file – with styled components (or emotion in this case, because I like it better):

import styled from "@emotion/styled"

const Header = styled.div`
  color: red;
`

<Header>My Headline</Header>

That's a lot of ways to do the exact same thing, right? While each of these implementations have their uses, sometimes you don't want to include additional stylesheets, inline code, or another component to do a tweak to ALL of the items on your site.

Moving on to our root_wrapper.js file, here's where we're going to define all of our items to be transformed into something else on every instance across your entire site.

I'll go ahead and show a simple example of the complete file and then explain what things do:

import React from 'react';
import { MDXProvider } from '@mdx-js/react';
import ReactHtmlParser from 'react-html-parser';

const components = {
  h1: ({ children }) => (
    <div style={{color: "red"}}><h1>{children}</h1></div>
  ),
  h2: ({ children }) => (
    <div style={{color: "blue"}}><h2>{children}</h2></div>
  ),
  'p.inlineCode': ({ children, props }) => (
    <code className="code_inline" {...props}>{ReactHtmlParser(children)}</code>
  ),
};

export const wrapRootElement = ({ element }) => (
  <MDXProvider components={components}>{element}</MDXProvider>
);
  • First, import React
  • Next we're importing the MDXProvider, which is going to take things that we've plopped into our source file and do the processing of the rules we define for each object.
  • I'm also importing the React HTML Parser here because I'll be using it to process some code below.

Breaking down the objects

For the objects that we want to change, we'll make a constant called components and define each item inside of the brackets:

const components = {...}

For the object itself, since our first element is an h1, which is a DOM base element, we'll use an arrow function in the following format:

h1: ({ children }) => (
  <div style={{color: "red"}}><h1>{children}</h1></div>
)

Basically, all we're really doing here is returning the {children}, and wrapping it in a div.

Children... what?

Imagine that your original object is &lt;h1&gt;My Headline&lt;/h1&gt;. Well, the "My Headline" part of it is a child of the h1 tag. What made it click for me initially was to think of it as:

  • wrapper tag = parent
  • stuff inside the wrapper = child

In this case, we're just adding inline style rules to the tag, globally. Really, you could add anything you wanted here: styles, a styled component, css classes, other javascript functions, etc.

Exporting your components

Once you have your styles and such ready to go, the last part of our wrapper file is the export. You don't need to change anything here, as it's just exporting the wrapRootElement and the conversions made to your input tag here, and making it available in your page or component where your original tag lives.

export const wrapRootElement = ({ element }) => (
  <MDXProvider components={components}>{element}</MDXProvider>
);

As we discussed before, MDXProvider is doing the work here, passing the original object through the edits being made in the components const.

What about that last paragraph object?

I didn't forget about our paragraph, but it does open up another discussion about wrapping elements that shows the possibilities of a global wrapper - code syntax highlighting. Let's take a look at that paragraph object:

'p.inlineCode': ({ children, props }) => (
  <code className="code_inline" {...props}>{ReactHtmlParser(children)}</code>
),

Two things to notice here, right from the start:

  • We're using a much more specific tag, than our h1 or h2 tag. The 'p.inlineCode' is targeting anything INSIDE of a paragraph tag that has the &lt;code&gt;&lt;/code&gt; tags surrounding it.
  • I'm passing both the children and the props of the object through our wrapper.

See those little code bits all around that are highlighted in yellow? This is the code bits that are doing that work for us. In the list above, I'm using a similar targeted tag, only this one is targeting list items that have the &lt;code&gt;&lt;/code&gt; tags surrounding it. It has a specific class added to it in the wrapper that turns list code items blue, instead of the 'p.inlineCode' yellow highlight.

Props

Now, the props object is an interesting one, if you're not familiar with how react handles passing items between components. Props is just short for property, props for properties. Basically, we can add additional "stuff" to our parent tag(s) and they will be passed to our component that's doing the work. In this case, we would be passing any prop that lives on the parent tag through our wrapper, and passing it down to the final rendered object. This is super helpful for adding additional context or passing info through to help with processing your item.

Here is an example of passing a custom prop to a component. In this case, we have a copy wrapper component that gets imported into our page layout template. In the page template, we have defined a const that will be passed to the component:

import React from 'react';
import { CopyWrapper } from '../components/global/copy_wrapper';

const Page = ({ children, ...props }) => {
  const additional_class = "my_class_to_add";
  return (    
    <div>
      <CopyWrapper wrapperclass={additional_class}>
        Some copy to wrap
      </CopyWrapper>

      {/* the rest of my content */}
    </div>
  )
}
export default Page

We're passing our additional_class const along to the component by using wrapperclass={additional_class}. Since the const is named wrapperclass, that's what will be used when referencing the prop inside of the component:

import React from 'react';

export const CopyWrapper = ({ children, ...props }) => {
  return (
    <div className={`copy ${props.wrapperclass}`}>
      {children}
    </div>
  )
}

Since the prop is being imported directly into the className, we need to interpolate the value of our prop that's being passed in. We do that by wrapping the prop name of wrapperclass with ${ }. And since the entire prop element is being imported (because in the future, we might have more than one prop that's being attached), we prefix our imported class with props. In the end, we get ${props.wrapperclass}, which will expand to my_class_to_add and be attached to the className.

To learn more about passing whole props objects – ...props in this case (it's called using a "spread" operator), check out the jsx in depth page for more info.

So, what we're left with after passing our prop into the component, and the final page is rendered – is this bit of html:

<div>
  <div class="copy my_class_to_add">
    Some copy to wrap
  </div>

  <!-- the rest of my content -->
</div>

Back to our paragraph – we've passed both children and props objects along (using an arrow function). We then pass any additional props (using the spread operator) directly into the parent tag using {...props}. Inside of the parent tags, we pass our original children to the ReactHTMLParser, so that the code that's passed along will be processed as actual code, not just a plain string.

<code className="code_inline" {...props}>{ReactHtmlParser(children)}</code>
A simple example

Since this concept seems rather difficult to visualize at first, here's a simple paragraph with some words surrounded by &grave;&nbsp;&grave;, which will highlight the contents as code:

Mr. Crusher, ready a `collision course` with the Borg ship.

The code that is generated looks like this:

<p>Mr. Crusher, ready a <code class="code_inline">collision course</code> with the Borg ship.</p>

How easy is that?

Wrapping it up

Initially, understanding the concept of using a wrapper to modify elements seems a bit daunting. It did for me awhile back. But once you understand the basic concept of how it works, you're free to expand on it and go crazy.

I currently use my wrapper to process all of my header elements, inline code elements, AND fenced code blocks. All of the code displayed above is done using simple tags inside of my mdx file. While the implementation isn't perfect, nor is it simple, what I've highlighted above should get anyone started on the right path. I'm sure that I'll have another note explaining all about how I did my code wrapper component as well. It would be helpful to write out more in depth, since this won't be the last time that I'll need to implement it on a site.

So, again – here's a list of components that you can target in your own wrapper, using MDXProvider. It's a pretty broad list of tags, and it opens up the possibility of doing all kinds of interesting things to your pages, without typing in the same code over and over and over again. Have fun!

  • ©2021 ff4500