How can i provoke a revaluation of a react component when user copies something on their ClipBoard?

97 Views Asked by At

I am trying to build a component with some validations depending on user's clipboard.

import { useState } from 'react'

export default function useCopiedFromClipboard() {
  const [copiedData, setCopiedData] = useState<string>('')

    navigator.clipboard
      .readText()
      .then((text) => {
        setCopiedData(text)
      })
      .catch((err) => {
        console.error('Failed to read clipboard contents: ', err)
      })

  return { copiedData }
}

So what I really want to do is use this hook inside a component.

import React from 'react'

export default function MyComponent() {
  const {copiedData} = useCopiedFromClipboard()
  
  useEffect(()=>{
    console.log('Run')
  },[copiedData])
  
  return (
    <></>
  )
}

I am expecting this useEffect to run everytime the user copies something to their clipboard. But this is not working.

Does anyone have any idea of how to implement this one ?

3

There are 3 best solutions below

0
Starcc On
import { useState, useEffect } from 'react'

export default function useCopiedFromClipboard() {
  const [copiedData, setCopiedData] = useState<string>('')

  useEffect(() => {
    const handleClipboardChange = () => {
      navigator.clipboard
        .readText()
        .then((text) => {
          setCopiedData(text)
        })
        .catch((err) => {
          console.error('Failed to read clipboard contents: ', err)
        })
    }

    document.addEventListener('paste', handleClipboardChange)

    return () => {
      document.removeEventListener('paste', handleClipboardChange)
    }
  }, [])

  return { copiedData }
}

Hope this helps you

5
RubenSmn On

The problem with your code is that you're only getting the value of the clipboard once, when you initialize the useCopiedFromClipboard hook for the first time. It does not 'listen' for more clipboard events.

We can solve this by using a useEffect where we setup a listener on the copy event. When the event first we manually get the selected text in the document and set that value to the users clipboard. Then we can update the state with the same value.

import { useState, useEffect } from "react";

export default function useCopiedFromClipboard() {
  const [copiedData, setCopiedData] = useState<string>("");

  useEffect(() => {
    const handleCopy = (e: ClipboardEvent) => {
      const selection = document.getSelection() ?? "";
      const data = selection.toString();
      e.clipboardData?.setData("text/plain", data);
      setCopiedData(data);
    };

    document.addEventListener("copy", handleCopy);

    return () => {
      document.removeEventListener("copy", handleCopy);
    };
  }, []);

  const onCopy = (text: string) => {
    navigator.clipboard.writeText(text);
    setCopiedData(text);
  };

  return { copiedData, onCopy };
}

We can use onCopy as follows

const { copiedData, onCopy } = useCopiedFromClipboard();

return (
  <>
    <button onClick={() => onCopy("something")}>copy</button>
    {copiedData && <p>{copiedData}</p>}
  </>
);

You can add some try catch as you wish, this should cover the bare logic.

0
Konstantinos Alexis On

Here is the solution that I chose for my problem. Its not that I love it but at least it works between different tabs, and thats super important for my application.

import { useEffect, useState } from 'react'

export default function useCopiedFromClipboard() {
  const [copiedData, setCopiedData] = useState<string>('')

  useEffect(() => {
    const timer = setInterval(async function () {
      if (await document.hasFocus()) {
        if (location.protocol === 'https:') {
          navigator.clipboard.readText().then((text) => {
            setCopiedData(text)
          })
        } else {
          clearInterval(timer)
        }
      }
    }, 1000)
    return () => {
      clearInterval(timer)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return { copiedData }
}