Gatsby MDX: Copy code button with confetti effect
Tags: gatsby, react, mdx
Published: 2020-08-24 13:10:49

---
title: "Gatsby MDX: Copy code button with confetti effect"
published: true
description: Create a copy to clipboard button with confetti effect for Gatsby blog with MDX support and react-prism-renderer module
cover_image: "https://storage.googleapis.com/brandflow-bucket/personal/blog/gatsby-mdx-copy-code-button-with-confetti.jpg"
tags: ["gatsby", "react","mdx"]
canonical_url: https://jimraptis.com/blog/gatsby-mdx-copy-code-button-with-confetti/
---

**TL;DR**

We'll build a versatile Copy to clipboard button for a Gatsby MDX blog. Because code is boring, we'll add a little bit of flair with a confetti explosion

---

A quick preview of what we'll build in this tutorial.

{% twitter 1292102594253467648 %}

Before starting, you can check the [live example](https://gatsby-mdx-code-copy-button.netlify.app/hi-folks/).

## Installation

We'll create a new Gatsby project from this [starter template](https://github.com/hagnerd/gatsby-starter-blog-mdx) that has built-in support for an MDX blog and the prism-react-renderer module.

```bash
gatsby new gatsby-starter-blog-mdx https://github.com/hagnerd/gatsby-starter-blog-mdx
```

## Understand the basics of MDX

The entry point for MDX is the `MDXProvider` component that handles internally the mapping of components to MDX. Also, it has a very important prop.

The `components` prop is an object that allows you to override the default component for each HTML element ([here is a list for them](https://mdxjs.com/table-of-components)) or even provide your own as shortcodes.

The Gatsby template uses the `MDXProvider` inside the `wrapRootElement` browser API.

<Info>
    The wrapRootElement browser API is useful to set up any Provider components that will wrap your application. 
</Info>

Below you see the `wrap-root-element.js` file that sets up the `MDXProvider` and overrides the `pre` element with a custom `Code` component.

```jsx
import React from "react"
import { MDXProvider } from "@mdx-js/react"
import { Code } from "./src/components/code"
import { preToCodeBlock } from "mdx-utils"

const components = {
  pre: preProps => {
    const props = preToCodeBlock(preProps)
    if (props) {
      return <Code {...props} />
    } else {
      return <pre {...preProps} />
    }
  },
}
export const wrapRootElement = ({ element }) => (
  <MDXProvider components={components}>{element}</MDXProvider>
)
```

Then, our wrapper is added to both `gatsby-browser` and `gatsby-ssr.js` files to render the root element of the Gatsby app.

```jsx
import { wrapRootElement as wrap } from "./wrap-root-element"

export const wrapRootElement = wrap
```

## Adjust the custom code component

The custom `Code` component lives in the `src/components/code.js` file and utilizes the `prism-react-renderer`. The `prism-react-renderer` is the perfect way to render some extra UI with your Prismjs-highlighted code.

The library tokenizes code using Prism and provides a small render-props-driven component to quickly render it out into React.

The default `code.js` is the following:

```jsx
import React from "react"
import { render } from "react-dom"
import Highlight, { defaultProps } from "prism-react-renderer"
import { LiveProvider, LiveEditor, LiveError, LivePreview } from "react-live"

export const Code = ({ codeString, language, ...props }) => {
  if (props["react-live"]) {
    return (
      <LiveProvider code={codeString} noInline={true}>
        <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>
    )
  }
}
```

The magic happens inside the `Highlight` component. The `pre` element renders the code wrapper and the render-prop functions provide the necessary props for each line and token/word.

The copy to clipboard button will live inside the `pre` element.

## Create the copy button

The copy button will be placed at the top right corner of the code section.

To achieve that positioning, we'll set the position of the `pre` element to `relative` and add a little bit of padding.

```jsx
<pre
  className={className}
  style={{
    ...style,
    padding: "2rem",
    position: "relative",
  }}
>
  ...
</pre>
```

The `Button` component is a simple button element that is placed with `position: absolute`:

```jsx
const Button = props => (
  <button
    style={{
      position: "absolute",
      top: 0,
      right: 0,
      border: "none",
      boxShadow: "none",
      textDecoration: "none",
      margin: "8px",
      padding: "8px 12px",
      background: "#E2E8F022",
      color: "white",
      borderRadius: "8px",
      cursor: "pointer",
      color: "#E2E8F0",
      fontSize: "14px",
      fontFamily: "sans-serif",
      lineHeight: "1",
    }}
    {...props}
  />
)
```

For a better UX, your users should be informed about the outcome of their actions. So it's a nice extra feature to toggle the button's text once the code is copied.

Then, we have to set a React hook state variable `isCopied`.

```jsx
const [isCopied, setIsCopied] = React.useState(false)
```

The `isCopied` variable gets true when the user clicks the copy button and resets to false after a specific amount of time (eg. 3 seconds).

```jsx
<Button
  onClick={() => {
    copyToClipboard(codeString)
    setIsCopied(true)
    setTimeout(() => setIsCopied(false), 3000)
  }}
>
  {isCopied ? "🎉 Copied!" : "Copy"}
</Button>
```

The `copyToClipboard` is our core functionality here. I've re-used a function from [this article](https://www.30secondsofcode.org/blog/s/copy-text-to-clipboard-with-javascript).

```js
const copyToClipboard = str => {
  const el = document.createElement("textarea")
  el.value = str
  el.setAttribute("readonly", "")
  el.style.position = "absolute"
  el.style.left = "-9999px"
  document.body.appendChild(el)
  el.select()
  document.execCommand("copy")
  document.body.removeChild(el)
}
```

## The final code component

By now, we have the custom `Code` component, the `copyToClipboard` function, and the `Button` component. Then, the final code component is the following:

```jsx
export const Code = ({ codeString, children, language, ...props }) => {
  const [isCopied, setIsCopied] = React.useState(false)

  if (props["react-live"]) {
    return (
      <LiveProvider code={codeString} noInline={true}>
        <LiveEditor />
        <LiveError />
        <LivePreview />
      </LiveProvider>
    )
  } else {
    return (
      <Highlight
        {...defaultProps}
        code={codeString}
        language={language}
        theme={dracula}
      >
        {({ className, style, tokens, getLineProps, getTokenProps }) => (
          <pre
            className={className}
            style={{
              ...style,
              padding: "2rem",
              position: "relative",
            }}
          >
            <Button
              onClick={() => {
                copyToClipboard(codeString)
                setIsCopied(true)
                setTimeout(() => setIsCopied(false), 3000)
              }}
            >
              {isCopied ? "🎉 Copied!" : "Copy"}
            </Button>

            {tokens.map((line, i) => (
              <div {...getLineProps({ line, key: i })} style={style}>
                {line.map((token, key) => (
                  <span {...getTokenProps({ token, key })} />
                ))}
              </div>
            ))}
          </pre>
        )}
      </Highlight>
    )
  }
}
```

## The Confetti party

The code works fine. The copy to clipboard functionality is perfect. But we still miss the flair!

There is a known secret in the frontend development community!

![Shhh GIF](https://media.giphy.com/media/NdKVEei95yvIY/giphy.gif)

> Everything is better with a little confetti

It's so useless but we're gonna congratulate our readers with a confetti rain.

To bring this into life, we'll have to install the React dependency `react-dom-confetti`.

```bash
    yarn add react-dom-confetti
```

The configuration is pretty straightforward. It's just a JSON object with [a couple of options](https://github.com/daniel-lundin/react-dom-confetti#config):

```js
const config = {
  angle: 90,
  spread: 360,
  startVelocity: 40,
  elementCount: 70,
  dragFriction: 0.12,
  duration: 3000,
  stagger: 3,
  width: "10px",
  height: "10px",
  perspective: "500px",
  colors: ["#a864fd", "#29cdff", "#78ff44", "#ff718d", "#fdff6a"],
}
```

The next step is to add the `Confetti` component. This component explodes to a confetti rain every time the prop `active` is true.

Then, we just have to pass the isCopied variable to make it explode in our example. Easy, huh?

```jsx
<Confetti active={isCopied} config={config} />
```

To place the Confetti component, we can use the same positioning trick as before. Because we want to fire the explosion in front of the button.

We'll set up a `Wrapper` component for the `Confetti` and `Highlight` components with the CSS attribute `position: relative`. Then, we'll wrap the Confetti component with the `ConfettiWrapper` which is placed absolutely at the top right corner.

```jsx
<Wrapper>
  <Highlight>...</Highlight>

  <ConfettiWrapper>
    <Confetti active={isCopied} config={config} />
  </ConfettiWrapper>
</Wrapper>
```

And the code for the two wrappers:

```jsx
const Wrapper = props => <div style={{ position: "relative" }} {...props} />

const ConfettiWrapper = props => (
  <div style={{ position: "absolute", top: 0, right: 0 }} {...props} />
)
```

---

## That's all folks

Clone the [Github repository](https://github.com/dimitrisraptis96/copy-code-in-gatsby-blog) and don't forget to show me your creation by tagging me, [@d\_\_raptis](https://twitter.com/d__raptis) on Twitter 💪

---


**If you liked this post, you can follow me on [Twitter](https://twitter.com/d__raptis) where I share daily tips about coding, design and bootstrapping micro-startups.**