No reactJS, como copiar texto para a área de transferência?

147

Estou usando o ReactJS e, quando um usuário clica em um link, quero copiar um texto para a área de transferência.

Estou usando o Chrome 52 e não preciso oferecer suporte a outros navegadores.

Não vejo por que esse código não resulta na cópia dos dados na área de transferência. (a origem do snippet de código é de uma postagem do Reddit).

Estou fazendo isso errado? Alguém pode sugerir que existe uma maneira "correta" de implementar a cópia na área de transferência usando o reactjs?

copyToClipboard = (text) => {
  console.log('text', text)
  var textField = document.createElement('textarea')
  textField.innerText = text
  document.body.appendChild(textField)
  textField.select()
  document.execCommand('copy')
  textField.remove()
}
Duke Dougal
fonte
1
Você tentou usar soluções de terceiros, como clipboardjs.com ou github.com/zeroclipboard/zeroclipboard ?
EugZol
11
@EugZol Eu realmente prefiro escrever código do que adicionar outra dependência, assumindo que o código seja bastante pequeno.
Duke Dougal
Verifique estas respostas stackoverflow.com/questions/400212/…
elmeister 15/09/16
@elmeister a questão é específico para reactjs
Duke Dougal

Respostas:

180

Pessoalmente, não vejo a necessidade de uma biblioteca para isso. Observando http://caniuse.com/#feat=clipboard, ele é amplamente suportado agora, no entanto, você ainda pode fazer coisas como verificar se a funcionalidade existe no cliente atual e simplesmente ocultar o botão copiar, se não existir.

import React from 'react';

class CopyExample extends React.Component {

  constructor(props) {
    super(props);

    this.state = { copySuccess: '' }
  }

  copyToClipboard = (e) => {
    this.textArea.select();
    document.execCommand('copy');
    // This is just personal preference.
    // I prefer to not show the the whole text area selected.
    e.target.focus();
    this.setState({ copySuccess: 'Copied!' });
  };

  render() {
    return (
      <div>
        {
         /* Logical shortcut for only displaying the 
            button if the copy command exists */
         document.queryCommandSupported('copy') &&
          <div>
            <button onClick={this.copyToClipboard}>Copy</button> 
            {this.state.copySuccess}
          </div>
        }
        <form>
          <textarea
            ref={(textarea) => this.textArea = textarea}
            value='Some text to copy'
          />
        </form>
      </div>
    );
  }

}

export default CopyExample;

Atualização: reescrita usando ganchos de reação no React 16.7.0-alpha.0

import React, { useRef, useState } from 'react';

export default function CopyExample() {

  const [copySuccess, setCopySuccess] = useState('');
  const textAreaRef = useRef(null);

  function copyToClipboard(e) {
    textAreaRef.current.select();
    document.execCommand('copy');
    // This is just personal preference.
    // I prefer to not show the the whole text area selected.
    e.target.focus();
    setCopySuccess('Copied!');
  };

  return (
    <div>
      {
       /* Logical shortcut for only displaying the 
          button if the copy command exists */
       document.queryCommandSupported('copy') &&
        <div>
          <button onClick={copyToClipboard}>Copy</button> 
          {copySuccess}
        </div>
      }
      <form>
        <textarea
          ref={textAreaRef}
          value='Some text to copy'
        />
      </form>
    </div>
  );
}
Nate
fonte
26
Esta é a melhor resposta. Não devemos incentivar os desenvolvedores a usar pacotes para todas as pequenas coisas, a menos que precisem de suporte antigo ao navegador.
tugce
2
Apenas para constar: o único problema com isso é que, se você estiver tentando copiar texto que ainda não está em algum elemento de texto na página, precisará invadir um conjunto de elementos DOM, definir o texto, copiá-lo, e limpe-o. Isso é muito código para algo muito pequeno. Normalmente, eu concordo que os desenvolvedores não devem ser incentivados a instalar constantemente bibliotecas.
Christopher Ronning
3
Para esse problema específico, o texto já está em um elemento na página. Em que caso haveria um texto visível na página que você deseja copiar que não esteja em um elemento? Essa é uma questão totalmente diferente para a qual eu ficaria feliz em mostrar uma solução. Você não precisaria hackear nada com o react, apenas forneceria um elemento oculto em sua função de renderização que também contém o texto. Não há necessidade de criar elementos ad hoc.
Nate
2
Eu recebo este erro de texto datilografado:Property 'select' does not exist on type 'never'
Alex C
3
Recebo TypeError: textAreaRef.current.select não é uma função
pseudozach
118

Use essa função onClick simples em linha em um botão se desejar gravar dados de forma programática na área de transferência.

onClick={() => {navigator.clipboard.writeText(this.state.textToCopy)}}
Gary Vernon Grubb
fonte
3
navigator.clipboard não suporta todos os navegadores
Premjeet
8
Parece seu apoio bem sido para os principais navegadores em 2018 caniuse.com/#search=clipboard
gasolin
2
baseado no link que você forneceu, parece que sua única totalmente suportado no safari ...
Nibb
2
funciona melhor para o meu caso em que o texto a ser copiado não está na página. Obrigado
NSjonas
1
O suporte parcial é muito bom, portanto, é totalmente suportado na maioria dos casos de uso. E, como mencionado, esta é a melhor solução programática.
quer
40

Definitivamente, você deve considerar o uso de um pacote como o @Shubham acima, mas criei um código de funcionamento com base no que você descreveu: http://codepen.io/dtschust/pen/WGwdVN?editors=1111 . Funciona no meu navegador no chrome, talvez você possa ver se há algo que fiz lá que você perdeu ou se há alguma complexidade estendida em seu aplicativo que impede que isso funcione.

// html
<html>
  <body>
    <div id="container">

    </div>
  </body>
</html>


// js
const Hello = React.createClass({
  copyToClipboard: () => {
    var textField = document.createElement('textarea')
    textField.innerText = 'foo bar baz'
    document.body.appendChild(textField)
    textField.select()
    document.execCommand('copy')
    textField.remove()
  },
  render: function () {
    return (
      <h1 onClick={this.copyToClipboard}>Click to copy some text</h1>
    )
  }
})

ReactDOM.render(
<Hello/>,
  document.getElementById('container'))
Drew Schuster
fonte
3
Por que um pacote é melhor que a sua solução?
Duke Dougal
6
Potencialmente melhor apoio cruz navegador, e mais olhos sobre o pacote em caso é necessário que haja bug corrigido
de Drew Schuster
Funciona como um encanto. Sim. Eu me pergunto sobre o suporte a vários navegadores também.
Karl Pokus
isso causaria um tremor na tela se, desde que você estiver usando appendChild, não importa a rapidez com que você o removerá depois?
robinnnnn
1
Isso é bom, mas não funciona no Chrome (72.0) no Android nem no FF (63.0) no Android.
Colin
35

A maneira mais simples será usar o react-copy-to-clipboardpacote npm.

Você pode instalá-lo com o seguinte comando

npm install --save react react-copy-to-clipboard

Use-o da seguinte maneira.

const App = React.createClass({
  getInitialState() {
    return {value: '', copied: false};
  },


  onChange({target: {value}}) {
    this.setState({value, copied: false});
  },


  onCopy() {
    this.setState({copied: true});
  },


  render() {
    return (
      <div>

          <input value={this.state.value} size={10} onChange={this.onChange} />

        <CopyToClipboard text={this.state.value} onCopy={this.onCopy}>
          <button>Copy</button>
        </CopyToClipboard>

                <div>
        {this.state.copied ? <span >Copied.</span> : null}
                </div>
        <br />

        <input type="text" />

      </div>
    );
  }
});

ReactDOM.render(<App />, document.getElementById('container'));

Uma explicação detalhada é fornecida no seguinte link

https://www.npmjs.com/package/react-copy-to-clipboard

Aqui está um violino correndo .

Shubham Khatri
fonte
Existe alguma solução se eu precisar fazer o inverso? O autor copiará o texto de um email para a área de texto no aplicativo reactjs. Não preciso reter tags html, no entanto, preciso preservar apenas quebras de linha.
TechTurtle
Você provavelmente precisará ligar o onpasteevento
Koen
Como posso usar este pacote se quiser copiar o conteúdo de uma tabela html para a área de transferência? @Shubham Khatri
Jane Fred
19

Por que usar você precisa de um pacote npm quando pode obter tudo em um único botão como este

<button 
  onClick={() =>  navigator.clipboard.writeText('Copy this text to clipboard')}
>
  Copy
</button>

Espero que isso ajude @jerryurenaa

jerryurenaa
fonte
16

Por que não usar apenas o método de coleta de dados clipboardData e.clipboardData.setData(type, content)?

Na minha opinião, é o método mais direto para conseguir empurrar o smth para dentro da área de transferência, verifique isso (usei isso para modificar dados durante a ação de cópia nativa):

...

handleCopy = (e) => {
    e.preventDefault();
    e.clipboardData.setData('text/plain', 'Hello, world!');
}

render = () =>
    <Component
        onCopy={this.handleCopy}
    />

Eu segui esse caminho: https://developer.mozilla.org/en-US/docs/Web/Events/copy

Felicidades!

EDIT: Para fins de teste, eu adicionei codepen: https://codepen.io/dprzygodzki/pen/ZaJMKb

Damian Przygodzki
fonte
3
@KarlPokus O questionador está procurando apenas a solução Chrome
TechTurtle
1
Testado na versão 62.0.3202.94 do Chrome. Está funcionando. codepen.io/dprzygodzki/pen/ZaJMKb
Damian Przygodzki
1
@OliverDixon é o objeto padrão do evento React. reactjs.org/docs/events.html
Damian Przygodzki
1
@DamianPrzygodzki Eu odeio elementos ocultos como esse, ótima maneira de confundir os desenvolvedores.
Oliver Dixon
1
@OliverDixon eu sinto você, mas acho bom se acostumar que às vezes existem alguns dados padrão aplicados ao método, especialmente em eventos.
Damian Przygodzki
8

Seu código deve funcionar perfeitamente, eu uso da mesma maneira. Certifique-se apenas de que, se o evento click for acionado a partir de uma tela pop-up, como um modal de inicialização ou algo assim, o elemento criado deverá estar dentro desse modal, caso contrário não será copiado. Você sempre pode fornecer o ID de um elemento nesse modal (como um segundo parâmetro) e recuperá-lo com getElementById e, em seguida, anexar o elemento recém-criado àquele em vez do documento. Algo assim:

copyToClipboard = (text, elementId) => {
  const textField = document.createElement('textarea');
  textField.innerText = text;
  const parentElement = document.getElementById(elementId);
  parentElement.appendChild(textField);
  textField.select();
  document.execCommand('copy');
  parentElement.removeChild(textField);
}
Kupi
fonte
8

Adotei uma abordagem muito semelhante à anterior, mas a tornei um pouco mais concreta. Aqui, um componente pai passará o URL (ou o texto que você quiser) como um suporte.

import * as React from 'react'

export const CopyButton = ({ url }: any) => {
  const copyToClipboard = () => {
    const textField = document.createElement('textarea');
    textField.innerText = url;
    document.body.appendChild(textField);
    textField.select();
    document.execCommand('copy');
    textField.remove();
  };

  return (
    <button onClick={copyToClipboard}>
      Copy
    </button>
  );
};
tjgragg
fonte
Isso foi útil porque eu queria ter uma tag de parágrafo em vez de Textarea
Ehsan Ahmadi
Obrigado! O único problema está ocultando o campo de
texto
3

Para as pessoas que estão tentando selecionar no DIV em vez do campo de texto, aqui está o código. O código é auto-explicativo, mas comente aqui se desejar mais informações:

     import React from 'react';
     ....

    //set ref to your div
          setRef = (ref) => {
            // debugger; //eslint-disable-line
            this.dialogRef = ref;
          };

          createMarkeup = content => ({
            __html: content,
          });

    //following function select and copy data to the clipboard from the selected Div. 
   //Please note that it is only tested in chrome but compatibility for other browsers can be easily done

          copyDataToClipboard = () => {
            try {
              const range = document.createRange();
              const selection = window.getSelection();
              range.selectNodeContents(this.dialogRef);
              selection.removeAllRanges();
              selection.addRange(range);
              document.execCommand('copy');
              this.showNotification('Macro copied successfully.', 'info');
              this.props.closeMacroWindow();
            } catch (err) {
              // console.log(err); //eslint-disable-line
              //alert('Macro copy failed.');
            }
          };

              render() {
                    return (
                        <div
                          id="macroDiv"
                          ref={(el) => {
                            this.dialogRef = el;
                          }}
                          // className={classes.paper}
                          dangerouslySetInnerHTML={this.createMarkeup(this.props.content)}
                        />
                    );
            }
connect2Coder
fonte
3

Aqui está outro caso de uso, se você deseja copiar o URL atual para a área de transferência:

Definir um método

const copyToClipboard = e => {
  navigator.clipboard.writeText(window.location.toString())
}

Chame esse método

<button copyToClipboard={shareLink}>
   Click to copy current url to clipboard
</button>
jasonleonhard
fonte
3

Melhor solução com ganchos de reação, sem necessidade de bibliotecas externas para isso

import React, { useState } from 'react';

const MyComponent = () => {
const [copySuccess, setCopySuccess] = useState('');

// your function to copy here

  const copyToClipBoard = async copyMe => {
    try {
      await navigator.clipboard.writeText(copyMe);
      setCopySuccess('Copied!');
    } catch (err) {
      setCopySuccess('Failed to copy!');
    }
  };

return (
 <div>
    <Button onClick={() => copyToClipBoard('some text to copy')}>
     Click here to copy
     </Button>
  // after copying see the message here
  {copySuccess}
 </div>
)
}

verifique aqui para obter mais documentação sobre a placa navigator.clip , a documentação navigator.clipboard A navigotor.clipboard é suportada por um grande número de navegadores.

Jaman-Dedy
fonte
2
import React, { Component } from 'react';

export default class CopyTextOnClick extends Component {
    copyText = () => {
        this.refs.input.select();

        document.execCommand('copy');

        return false;
    }

    render () {
        const { text } = this.state;

        return (
            <button onClick={ this.copyText }>
                { text }

                <input
                    ref="input"
                    type="text"
                    defaultValue={ text }
                    style={{ position: 'fixed', top: '-1000px' }} />
            </button>
        )
    }
}
Yash Pokar
fonte
1

Se você deseja selecionar a partir do DIV em vez do campo de texto, aqui está o código. O "código" é o valor que deve ser copiado

import React from 'react'
class CopyToClipboard extends React.Component {

  copyToClipboard(code) {
    var textField = document.createElement('textarea')
    textField.innerText = code
    document.body.appendChild(textField)
    textField.select()
    document.execCommand('copy')
    textField.remove()
  }
  render() {
    return (
      <div onClick={this.copyToClipboard.bind(this, code)}>
        {code}
      </div>

    )
  }
}

export default CopyToClipboard
Haris George
fonte
1
A melhor prática do SO é realizar seu código com uma explicação. Por favor faça.
MartenCatcher 21/01
0

aqui está o meu código:

import React from 'react'

class CopyToClipboard extends React.Component {

  textArea: any

  copyClipBoard = () => {
    this.textArea.select()
    document.execCommand('copy')
  }

  render() {
    return (
      <>
        <input style={{display: 'none'}} value="TEXT TO COPY!!" type="text" ref={(textarea) => this.textArea = textarea}  />
        <div onClick={this.copyClipBoard}>
        CLICK
        </div>
      </>

    )
  }
}

export default CopyToClipboard
Alan
fonte
0
<input
value={get(data, "api_key")}
styleName="input-wrap"
title={get(data, "api_key")}
ref={apikeyObjRef}
/>
  <div
onClick={() => {
  apikeyObjRef.current.select();
  if (document.execCommand("copy")) {
    document.execCommand("copy");
  }
}}
styleName="copy"
>
  复制
</div>
prumo
fonte
7
Adicione uma explicação de como esse código resolve o problema, em vez de apenas postar o código.
Alexander van Oostenrijk
0

Encontrou a melhor maneira de fazê-lo. quero dizer o caminho mais rápido: w3school

https://www.w3schools.com/howto/howto_js_copy_clipboard.asp

Dentro de um componente funcional de reação. Crie uma função chamada handleCopy:

function handleCopy() {
  // get the input Element ID. Save the reference into copyText
  var copyText = document.getElementById("mail")
  // select() will select all data from this input field filled  
  copyText.select()
  copyText.setSelectionRange(0, 99999)
  // execCommand() works just fine except IE 8. as w3schools mention
  document.execCommand("copy")
  // alert the copied value from text input
  alert(`Email copied: ${copyText.value} `)
}

<>
              <input
                readOnly
                type="text"
                value="[email protected]"
                id="mail"
              />
              <button onClick={handleCopy}>Copy email</button>

</>

Se não estiver usando o React, o w3schools também terá uma maneira legal de fazer isso com a dica de ferramenta incluída: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_copy_clipboard2

Se estiver usando o React, é interessante pensar: Use um Toastify para alertar a mensagem. https://github.com/fkhadra/react-toastify Esta é a biblioteca muito fácil de usar. Após a instalação, você poderá alterar esta linha:

 alert(`Email copied: ${copyText.value} `)

Para algo como:

toast.success(`Email Copied: ${copyText.value} `)

Se você quiser usá-lo, não se esqueça de instalar o toastify. importe ToastContainer e também brinda css:

import { ToastContainer, toast } from "react-toastify"
import "react-toastify/dist/ReactToastify.css"

e adicione o recipiente da torrada dentro do retorno.

import React from "react"

import { ToastContainer, toast } from "react-toastify"
import "react-toastify/dist/ReactToastify.css"


export default function Exemple() {
  function handleCopy() {
    var copyText = document.getElementById("mail")
    copyText.select()
    copyText.setSelectionRange(0, 99999)
    document.execCommand("copy")
    toast.success(`Hi! Now you can: ctrl+v: ${copyText.value} `)
  }

  return (
    <>
      <ToastContainer />
      <Container>
                <span>E-mail</span>
              <input
                readOnly
                type="text"
                value="[email protected]"
                id="mail"
              />
              <button onClick={handleCopy}>Copy Email</button>
      </Container>
    </>
  )
}
Iago Barreto
fonte
Sua resposta contém apenas a referência a outro recurso, mas nenhuma resposta específica. Se o w3schools se vincular como a solução correta, digite-o aqui.
f.khantsis 26/03