6

I'm working on React js, I created my app with create-react-app using npm. I was trying to build a button that takes an image and writes it to the clipboard. Fourtunately I found this npm library that seems to work fine! But keeps me thinking why I couldn't use the ¿built-in? Asynchronous Clipboard API to copy the image (the text copy works fine). I read a really enlightening guide here, and kept reading other great guide here, so I tried all the codes suggested, there and in other pages (despite they don't seem to really change the functionality, I got to try). I came with the same error in every try that impedes to compile: "'ClipboardItem' is not defined no-undef". One code for example was this one:

const response = await fetch('valid img url of a png image');
const blob = await response.blob();
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob})]);

It seems to be simple, easy to follow. The problem is when you need to put the data in a form the Clipboard can read it, make it a blob, because I need the ClipboardItem constructor, and my app seems to be unable to recognize it as such. Keeps returning ClipboardItem is not defined or, if I somehow define it, says it's not a constructor, of course. I tried with other constructors like Blob(), but had the same problem. The last thing kept me thinking that, since I'm new in the programming world, if there is something kinda basic I don't know of the interaction of Web Apis like this one with node or Reactjs, and if there is a solution, of course! Thanks in advance, you guys are great!

Edit: adding the whole component code as requested:

import React from "react";
  
function TestingClipAPI () { 

  async function handleScreenshot () {
    const response = await fetch('https://i.postimg.cc/d0hR8HfP/telefono.png');
    const blob = await response.blob();
    await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob})]);
  };
  
  return (
    <div>
     <button onClick={handleScreenshot} id="buttonID">test</button>
    </div>
  )
};

export default TestingClipAPI;

Possible issue: This might be because of CRA (Create-React-App) config - similar issue. Something like the library linked can be done, create a canvas and copy the image from there.

Solution or a way to make it work anyway: make a call this way before using ClipboardItem:

const { ClipboardItem } = window;

Note: this also works with other constructors like toBlob and HTMLCanvasElement that had the same issue.

Andres S
  • 81
  • 1
  • 1
  • 5

3 Answers3

5

Unfortunately, as of the time of this answer, ClipboardItem isn't supported in Firefox. (Support can be enabled via an about:config setting; but of course, most Internet users will not have done this.)

Source: https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem#browser_compatibility

Jon Schneider
  • 25,758
  • 23
  • 142
  • 170
  • What would be the workaround? – Fazwelington Oct 18 '22 at 21:45
  • 1
    @volley, I've had success using https://clipboardjs.com/. (No affiliation, I'm just a fan!) It's in use on my site https://clipemoji.com/, if you want to see a live example. – Jon Schneider Oct 19 '22 at 18:17
  • 1
    @volley, if you don't want to include an entire (albeit small) external script file in your project, I've also had success with the approach presented in this answer: https://stackoverflow.com/a/48020189/12484 – Jon Schneider Oct 19 '22 at 18:26
2

Things to look for:

  1. Browser support Clipboard
  2. Secure origin on HTTPS or localhost. See this post.
  3. How the function is being called - in the OP's case - onClick & asynchronous.

The issue is that onClick are not asynchronous by default and you are not awaiting the response and you also have a typo in navigator.clipboard.

  const handleScreenshot = async () => {
    try {
      const response = await fetch(
        "https://i.postimg.cc/d0hR8HfP/telefono.png"
      );
      const blob = await response.blob();
      await navigator.clipboard.write([
        new ClipboardItem({ "image/png": blob }),
      ]);
    } catch (err) {
      console.error(err);
    }
  }

return (
  <button onClick={async () => await handleScreenshot()} id="buttonID">
    test
  </button>
);

There are tradeoff between inline function and below are alternatives. I'd personally use the latter method.

function handleScreenshot() {
  async function screenShot() {
    try {
      const response = await fetch(
        "https://i.postimg.cc/d0hR8HfP/telefono.png"
      );
      const blob = await response.blob();
      await navigator.clipboard.write([
        new ClipboardItem({ "image/png": blob }),
      ]);
    } catch (err) {
      console.error(err);
    }
  }
  screenShot();
}

return (
  <button onClick={handleScreenshot} id="buttonID">
    test
  </button>
);

Lastly, you can return a chained promise.

Sean W
  • 5,663
  • 18
  • 31
  • 1
    Thanks Man! I appreciate your comment. I thought about the browser not supporting it, but I have the last version of Chrome and, if I'm not reading wrong the information at MDN it should support it, and I was running it in port/localhost 3000 with npm start. I take your advice on try, gonna learn a bit more about it. – Andres S Apr 06 '21 at 17:20
  • 1
    I'd need to see how you're setting up and calling your copy functions. Can you update your post to show the whole component with only the copy and paste functions and the JSX you return to the browser. – Sean W Apr 06 '21 at 19:47
  • 1
    sure dude, I just created a testing component, that works just fine with copy-img-clipboard library. I delayed my answer because I also tested it making my localhost secure, but it failed again. – Andres S Apr 07 '21 at 14:12
  • 1
    I updated the anwser- onClicks are not async by default. – Sean W Apr 07 '21 at 15:31
  • 1
    Thanks dude! I take your advice. I usually use promises in the chained way, do you know or have an opinion if it's better practice than try? The synchronous onClick doesnt seem to be the problem here, I tested that way, rewrote my code first, and copied your code later in case of a typo, and keeps failing the same way. I also have another onClick events that fetch and work fine by just making asynchronous the handle function. Thanks a lot man, I aprecciate your effort, I think it's kinda start itching to you too – Andres S Apr 07 '21 at 17:58
  • 1
    Try it now, I cut and pasted yours into my comment, I just put it in my typescript and you have "clipoboard" and it should be "clipboard" - notice the extra "o". I just ran the exact code from above in chrome and localhost and it worked for me. – Sean W Apr 07 '21 at 18:14
  • 1
    I'd use whatever method promise handling works for you. I prefer the way I posted above just because it's easier for me to follow and it tends to be cleaner. When you chain it can become verbose in some occasions. – Sean W Apr 07 '21 at 18:19
  • 1
    Thanks man, so careful. I had a busy day today, I just finished testing it, and the error remains, it should be something about my configuration. I can see what you say about promise handling, and share your opinion! (also corrected the code here, was wishing it was that haha) – Andres S Apr 09 '21 at 02:57
  • It has to be something else. I used the exact code from above to copy the phone icon at that URL and I was able to paste it into Microsoft Word. Hopefully, you figure it out. – Sean W Apr 09 '21 at 03:13
  • 1
    I tested it with a fresh create-react-app, I just imported that component and it keeps stunning on the same error. I'm starting to think my first guess wasn't so far from the answer maybe. Have you done any of your tests in react? – Andres S Apr 09 '21 at 04:05
  • 1
    I tested it in react with the latest version of chrome on localhost:3000 – Sean W Apr 09 '21 at 04:09
  • 1
    with npx create-react-app? It may be something on my basic configuration, I wouldn't know what – Andres S Apr 09 '21 at 11:12
  • 1
    I'm using next. I just tried a fresh CRA and it also didn't work for me, however, this might be because of CRA config - https://github.com/facebook/create-react-app/issues/10598. You could do something like the library you linked does - create a canvas and copy the image from there - https://github.com/LuanEdCosta/copy-image-clipboard/blob/master/src/index.ts – Sean W Apr 09 '21 at 15:16
  • 1
    Thanks a lot! It must be it, the CRA configuration. I will learn more about canvas, it seems it may save me some headaches in the future. – Andres S Apr 09 '21 at 16:52
  • 1
    Hey dude! I made it work! I called: "const { ClipboardItem } = window;" it works for toBlob, HTMLCanvasElement, Blob, atob, etc. Still think the problem is the CRA configuration by npm, but it works this way! – Andres S Apr 12 '21 at 23:30
2

Simply add window in front of ClipboardItem like the following

window.ClipboardItem(...)
Milos Rad
  • 49
  • 1
  • 3