Avatar

Nǐ hǎo, I'm Julia.

Creating a Copy to Clipboard Button in React

#hooks #react

4 min read

I'm currently working on a social sharing module that I can place at the bottom of my React app screens. The idea is to allow visitors to share my link on their socials with a quick click of a social icon. In addition to the usual suspects of Facebook, Twitter and Instagram, I also wanted to include a "copy to clipboard" option for visitors that didn't necessarily want to post directly on a social platform. Here's a step by step on how I created a...

Copy to clipboard button

My end goal was to have a component comprising an icon and text that reads "Copy link". Upon clicking on the component, the text should change to "Copied" and blink for a couple of seconds, before reverting back to "Copy link".

Step 1 - Javascript logic

I had no idea how to effect a copy to clipboard action in Javascript before starting this build. Turns out it's not that hard, but you have to do it in a slightly roundabout way. Essentially, what's happening is that you need to:

  • Create an input element in the DOM and set a value for it;
  • Append the input element to the document body, which then allows you to select it;
  • Run the copy command so that the value is now copied to clipboard; and
  • Remove this temporary input element you've just been created, from the DOM.
function copyToClipboard() {
  const tempInput = document.createElement('input')
  tempInput.value = 'https://bionicjulia.com/examplelink'
  document.body.appendChild(tempInput)
  tempInput.select()
  document.execCommand('copy')
  document.body.removeChild(tempInput)
}

Step 2 - Create the React component

The next step is to bundle this logic up into a React component and hook it to an onClick event. I've called the function copyToClipboard.

One thing to note is that I added a conditional check to first assess whether a user's browser even supports the copy command in the first place. This is done with the document.queryCommandSupported('copy') statement. If it doesn't, this entire component would not work and thus should not render anything visible.

export const CopyTextIcon: React.FC = () => {
  function copyToClipboard() {
    const tempInput = document.createElement('input')
    tempInput.value = 'https://bionicjulia.com/examplelink'
    document.body.appendChild(tempInput)
    tempInput.select()
    document.execCommand('copy')
    document.body.removeChild(tempInput)
  }

  return (
    <>
      {document.queryCommandSupported('copy') && (
        <div id="copy-icon" onClick={() => copyToClipboard()} className="cursor-pointer">
          <div className="mt-8 flex justify-center">
            <img src={`${appConfig.publicUrl}/img/social/copy-mobile.svg`} alt="Copy" />
            <p className="ml-2 text-base-secondary text-sm light">Copy link</p>
          </div>
        </div>
      )}
    </>
  )
}

Step 3 - Manage the copy text state

The next step was to manage the state of the "Copy link" text. To do this in React, I used the useState hook and coupled this with a timeout function within a useEffect hook.

const [copySuccess, setCopySuccess] = useState('')

useEffect(() => {
  setTimeout(() => setCopySuccess(''), 2000)
}, [copySuccess])

The other key step is to setCopySuccess state to 'Copied' upon the onClick event.

function copyToClipboard() {
  const tempInput = document.createElement('input')
  tempInput.value = 'https://bionicjulia.com/examplelink'
  document.body.appendChild(tempInput)
  tempInput.select()
  document.execCommand('copy')
  document.body.removeChild(tempInput)
  setCopySuccess('Copied')
}

To summarise what's happening here:

  • The initial state of copySuccess is a blank string '' ;
  • Upon clicking the button, the copySuccess state is set to 'Copied';
  • The useEffect hook kicks in and resets the copySuccess state to a blank string '' after 2 seconds.

Step 4: Animate the text

Finally, we hook up the "Copy link" text and make it dependent on the copySuccess state, and then add some blinking animation (I've called the class blink).

return (
  <>
    {document.queryCommandSupported('copy') && (
      <div id="copy-icon" onClick={() => copyToClipboard()} className="cursor-pointer">
        <div className="mt-8 flex justify-center">
          <img src={`${appConfig.publicUrl}/img/social/copy-mobile.svg`} alt="Copy" />
          <p className="ml-2 text-base-secondary text-sm light">
            {copySuccess ? <span className="blink">{copySuccess}</span> : 'Copy link'}
          </p>
        </div>
      </div>
    )}
  </>
)

Here's the simple CSS animation I created for the class.

.blink {
  animation: blinker 1s linear infinite;
}

@keyframes blinker {
  50% {
    opacity: 0.2;
  }
}

And that's it! No need for any external libraries. 🤓

© 2016-2024 Julia Tan · Powered by Next JS.