DEV Community

Cover image for Enhancing React Code Blocks for easy sharing
Bojan Jagetic
Bojan Jagetic

Posted on • Edited on • Originally published at bojanjagetic.com

Enhancing React Code Blocks for easy sharing

Introduction

Creating a Code Block with the ability to Copy to Clipboard in React

When displaying code snippets on a website or application, it's often useful to provide a way for users to quickly copy the code to their clipboard. In this tutorial, we'll show you how to add this functionality to a code block created with the react-syntax-highlighter library, using clipboard.js and react-icons with tailwindcss for styling.

Prerequisites

Before we get started, make sure you have the following libraries installed:

You can install these packages by running the following command in your project's root directory:

npm install react-syntax-highlighter clipboard react-icons tailwindcss
Enter fullscreen mode Exit fullscreen mode

or

yarn add react-syntax-highlighter clipboard react-icons tailwindcss
Enter fullscreen mode Exit fullscreen mode

NOTE I am using all this modules in my specific case, for my blog/portfolio website (you can check there how it looks in real example) but you do not have to use all of this, also you can use similar modules like clipboard.js or react-hot-toast for example.

Import Required Libraries

At the top of your component file, import the libraries you'll need:

import React, { useState } from 'react'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { IoIosCopy, IoIosCheckmarkCircleOutline } from 'react-icons/io'
import { vs2015 } from 'react-syntax-highlighter/dist/cjs/styles/hljs'
import { ToastContainer, toast } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
import { CopyToClipboard } from 'react-copy-to-clipboard'
Enter fullscreen mode Exit fullscreen mode

You have variation of themes for react-syntax-highlighter depending if you are using hljs or prisma. Here you can find all available themes for hljs and for prisma.

Create the Code Block Component

In the component that will display the code block, create a functional component called CodeBlock that takes in a code prop.

const CodeBlock = ({ code }) => {
  //...
  return (
    <div>
      {/* code block and button goes here */}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Create the Copy to Clipboard Button

In the CodeBlock component, create a button that the user can click to initiate the copy action. It should include the FaClipboard icon and classes from tailwindcss that position the button in the top right corner of the code block.

return (
    <div className="relative">
        <button
            className="absolute flex flex-row  top-0 right-0 p-2">
            <span className='m-1 pb-1 basis-3/4 text-xs'>{language}</span>
            <CopyToClipboard text='Hello World !'>
            </CopyToClipboard>
        </button>
    </div>
)
Enter fullscreen mode Exit fullscreen mode

In this example, relative and absolute classes from tailwindcss are used to position the button with the FaClipboard icon in the top-right corner of the code block. The p-2 class is for padding and flex flex-row are used to position language name next to copy to clipboard icon.

Adding functionalities

In most cases we will have different values to copy and different languages, in this case we will use variable for code and language properties. Also you will maybe want to show toast message to user that code is copied in this example we will use react-toastify, so lets expand our code with copy functionality, language and code variables.

Using variables

So we have provided code to our CodeBlock now we want to pass it to our SyntaxHighlighter and CopyToClipboard where we will copy it to the clipboard.

const CodeBlock = ({ code, language }) => {
 const notify = () => {
    toast(<ToastDisplay className='bg-neutral-700 m-2' />)
  }

  function ToastDisplay () {
    return (
            <div className='m-2'>
                <p className='text-md'>Copied to clipboard !</p>
            </div>
    )
  };

}
Enter fullscreen mode Exit fullscreen mode

Add syntax highlighter component

Now that you have the button, we want to show our code which will be copied, for this we are using react-syntax-highlighter component to which we will pass our code.

 return (
        <div className="relative">
            <button
                className="absolute flex flex-row  top-0 right-0 p-2">
                <span className='m-1 pb-1 basis-3/4 text-xs'>javascript</span>
                <CopyToClipboard text={code}></CopyToClipboard>
            </button>
            <SyntaxHighlighter
                className=''
                language='javascript'
                style={vs2015}
                wrapLines={true}
                wrapLongLines={true}
                showLineNumbers={false}
                showInlineLineNumbers={false}
            >
                {code}
            </SyntaxHighlighter>
        </div>
  )
Enter fullscreen mode Exit fullscreen mode

Sending toast

After we successfully copy we want to notify and let end user know that code is successfully copied to clipboard.

const CodeBlock = ({ code, className }) => {
  const notify = () => toast(<ToastDisplay className='bg-neutral-700 m-2' />)

  function ToastDisplay () {
    return (
            <div className='m-2'>
                <p className='text-md'>Copied to clipboard !</p>
            </div>
    )
  };

  return (
        <div className="relative">
            <button
                className="absolute flex flex-row  top-0 right-0 p-2">
                <span className='m-1 pb-1 basis-3/4 text-xs'>{language}</span>
                <CopyToClipboard text={code}
                    onCopy={() => notify()}>
                       <IoIosCopy className="text-lg m-1 basis-1/4 hover:text-white" />
                </CopyToClipboard>
            </button>
            <SyntaxHighlighter
                className=''
                language={parseLanguageByClass(className)}
                style={vs2015}
                wrapLines={true}
                wrapLongLines={true}
                showLineNumbers={false}
                showInlineLineNumbers={false}
            >
                {code}
            </SyntaxHighlighter>
            <ToastContainer
                position="bottom-right"
                autoClose={5000}
                hideProgressBar
                newestOnTop={false}
                closeOnClick={false}
                closeButton={false}
                limit={1}
                rtl={false}
                pauseOnFocusLoss={false}
                draggable={false}
                pauseOnHover={false}
                theme="dark" />
        </div>
  )

}
Enter fullscreen mode Exit fullscreen mode

Change icon after copying

This one is optional as well, you can use it but if you don't like it just skip this step. As seen on many examples and websites, I like more to have icon changed after code is copied, so initially we have copy icon and after it gets copied we will change it to checkmark icon which will last for 5 seconds and after that it will be reverted back to copy icon.

  const [copied, setCopied] = useState(false)
  const notify = () => {
    toast(<ToastDisplay className='bg-neutral-700 m-2' />)
    copy()
  }
  ...

  const copy = () => {
    console.log('Copied!')
    setCopied(true)
    setTimeout(() => {
      setCopied(false)
    }, 5000)
  }

  return (
        <div className="relative">
            <button
                className="absolute flex flex-row  top-0 right-0 p-2">
                <span className='m-1 pb-1 basis-3/4 text-xs'>{language}</span>
                <CopyToClipboard text={code}
                    onCopy={(copied) => notify()}>
                        {copied
                          ? <IoIosCheckmarkCircleOutline className="text-lg m-1 text-green-500 basis-1/4 " />
                          : <IoIosCopy className="text-lg m-1 basis-1/4 hover:text-white" />}
                </CopyToClipboard>
    ...
  )

Enter fullscreen mode Exit fullscreen mode

Conclusion

That's All Folks, we have the whole component up and running. If you had problem catching up, here you can check the whole code :

import React, { useState } from 'react'
import SyntaxHighlighter from 'react-syntax-highlighter'
import { IoIosCopy, IoIosCheckmarkCircleOutline } from 'react-icons/io'
import { vs2015 } from 'react-syntax-highlighter/dist/cjs/styles/hljs'
import { ToastContainer, toast } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
import { CopyToClipboard } from 'react-copy-to-clipboard'

const CodeBlock = ({ code, language }) => {
  const [copied, setCopied] = useState(false)
  const notify = () => {
    toast(<ToastDisplay className='bg-neutral-700 m-2' />)
    copy()
  }

  function ToastDisplay () {
    return (
            <div className='m-2'>
                <p className='text-md'>Copied to clipboard !</p>
            </div>
    )
  };
  const copy = () => {
    console.log('Copied!')
    setCopied(true)
    setTimeout(() => {
      setCopied(false)
    }, 5000)
  }

  return (
        <div className="relative">
            <button
                className="absolute flex flex-row  top-0 right-0 p-2">
                <span className='m-1 pb-1 basis-3/4 text-xs'>{language}</span>
                <CopyToClipboard text={code}
                    onCopy={(copied) => notify()}>
                        {copied
                          ? <IoIosCheckmarkCircleOutline className="text-lg m-1 text-green-500 basis-1/4" />
                          : <IoIosCopy className="text-lg m-1 basis-1/4 hover:text-white" />}

                </CopyToClipboard>

            </button>
            <SyntaxHighlighter
                className=''
                language={language}
                style={vs2015}
                wrapLines={true}
                wrapLongLines={true}
                showLineNumbers={false}
                showInlineLineNumbers={false}
            >
                {code}
            </SyntaxHighlighter>
            <ToastContainer
                position="bottom-right"
                autoClose={5000}
                hideProgressBar
                newestOnTop={false}
                closeOnClick={false}
                closeButton={false}
                limit={1}
                rtl={false}
                pauseOnFocusLoss={false}
                draggable={false}
                pauseOnHover={false}
                theme="dark" />
        </div>
  )
}
export default CodeBlock
Enter fullscreen mode Exit fullscreen mode

You can check final look on my personal blog or you can check video example how this really look

Remember, there is many different variation on this topic, you can use whatever module you like in order to look how you imagined. Its pretty straight-forward solution and easy to implement, but if you have any doubts or questions feel free to contact me.

Top comments (0)