En reactJS, ¿cómo copiar texto al portapapeles?

147

Estoy usando ReactJS y cuando un usuario hace clic en un enlace, quiero copiar algo de texto en el portapapeles.

Estoy usando Chrome 52 y no necesito admitir ningún otro navegador.

No puedo ver por qué este código no da como resultado que los datos se copien en el portapapeles. (el origen del fragmento de código es de una publicación de Reddit).

¿Estoy haciendo esto mal? ¿Alguien puede sugerir que hay una forma "correcta" de implementar la copia al portapapeles usando 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()
}
Duque dugal
fuente
1
¿ Intentó usar soluciones de terceros, como clipboardjs.com o github.com/zeroclipboard/zeroclipboard ?
EugZol
11
@EugZol Realmente prefiero escribir código en lugar de agregar otra dependencia, suponiendo que el código sea bastante pequeño.
Duke Dougal
Compruebe estas respuestas stackoverflow.com/questions/400212/…
elmeister
@elmeister la pregunta es específica para reaccionar
Duke Dougal

Respuestas:

180

Personalmente, no veo la necesidad de una biblioteca para esto. Mirando http://caniuse.com/#feat=clipboard ahora es bastante compatible, sin embargo, aún puede hacer cosas como verificar si la funcionalidad existe en el cliente actual y simplemente ocultar el botón de copia si no es así.

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;

Actualización: Reescrito usando React Hooks en 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
fuente
26
Esta es la mejor respuesta. No deberíamos alentar a los desarrolladores a usar paquetes para cada pequeña cosa a menos que necesiten soporte de navegador antiguo.
Tugce
3
Solo para el registro: el único problema con esto es que si está intentando copiar texto que aún no está en algún elemento de texto en la página, deberá piratear un conjunto de elementos DOM, configurar el texto, copiarlo, y limpiarlo Eso es mucho código para algo muy pequeño. Normalmente, estaría de acuerdo en que no se debe alentar a los desarrolladores a instalar constantemente bibliotecas.
Christopher Ronning
3
Para este problema en particular, el texto ya está en un elemento de la página. ¿En qué caso habría texto visible en la página que desea copiar que no esté en un elemento? Esa es una cuestión completamente diferente a la que me encantaría mostrar una solución. No necesitaría hackear nada con react, simplemente proporcionaría un elemento oculto en su función de renderizado que también contiene el texto. No es necesario crear elementos ad hoc.
Nate
2
Me sale este error mecanografiado:Property 'select' does not exist on type 'never'
Alex C
3
Me sale TypeError: textAreaRef.current.select no es una función
pseudozach
118

Utilice esta sencilla función onClick en línea en un botón si desea escribir datos mediante programación en el portapapeles.

onClick={() => {navigator.clipboard.writeText(this.state.textToCopy)}}
Gary Vernon Grubb
fuente
3
navigator.clipboard no es compatible con todos los navegadores
Premjeet
8
Parece que ha sido un buen soporte para los principales navegadores en 2018 caniuse.com/#search=clipboard
gasolin
2
según el enlace que proporcionó, parece que solo es totalmente compatible en safari ...
Nibb
2
funciona mejor para mi caso de uso donde el texto para copiar no está realmente en la página. Gracias
NSjonas
1
El soporte parcial es muy bueno, por lo que es totalmente compatible con la mayoría de los casos de uso. Y como se mencionó, esta es la mejor solución programática.
Dror Bar
40

Definitivamente deberías considerar usar un paquete como @Shubham anterior, es aconsejable, pero creé un codepen funcional basado en lo que describiste: http://codepen.io/dtschust/pen/WGwdVN?editors=1111 . Funciona en mi navegador en Chrome, tal vez puedas ver si hay algo que hice allí que te perdiste, o si hay alguna complejidad extendida en tu aplicación que impide que esto 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
fuente
3
¿Por qué es un paquete mejor que su solución?
Duke Dougal
66
Potencialmente un mejor soporte transversal navegador, y más ojos en el paquete en caso de que necesitan estar error corregido
dibujó Schuster
Funciona de maravilla. Si. Me pregunto acerca de la compatibilidad entre navegadores también.
Karl Pokus
¿Causaría un parpadeo en la pantalla si estás usando appendChild, sin importar qué tan rápido lo elimines después?
robinnnnn
1
Esto es bueno, pero no funciona en Chrome (72.0) en Android ni en FF (63.0) en Android.
Colin
35

La forma más sencilla será usar el react-copy-to-clipboard paquete npm.

Puedes instalarlo con el siguiente comando

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

Úselo de la siguiente manera.

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'));

Se proporciona una explicación detallada en el siguiente enlace.

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

Aquí hay un violín corriendo .

Shubham Khatri
fuente
¿Hay alguna solución si necesito hacer lo contrario? es decir, el autor copiará el texto de un correo electrónico al área de texto en la aplicación reactjs. No necesito retener etiquetas html, sin embargo, necesito preservar solo saltos de línea.
TechTurtle
Probablemente necesites conectar el onpasteevento
Koen
¿Cómo puedo usar este paquete si quiero copiar el contenido de una tabla html al portapapeles? @Shubham Khatri
Jane Fred
19

¿Por qué usar necesita un paquete npm cuando puede obtener todo en un solo botón como este?

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

Espero que esto ayude a @jerryurenaa

jerryurenaa
fuente
16

¿Por qué no utilizar solo el método de recopilación de clipboardData de eventos? e.clipboardData.setData(type, content) ?

En mi opinión, es el método más directo para lograr empujar algo dentro del portapapeles, mira esto (lo he usado para modificar datos mientras se realiza una acción de copia nativa):

...

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

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

Seguí ese camino: https://developer.mozilla.org/en-US/docs/Web/Events/copy

¡Salud!

EDITAR: para fines de prueba, he agregado codepen: https://codepen.io/dprzygodzki/pen/ZaJMKb

Damian Przygodzki
fuente
3
@KarlPokus El interlocutor solo está buscando una solución de Chrome
TechTurtle
1
Probado en Chrome versión 62.0.3202.94. Está funcionando. codepen.io/dprzygodzki/pen/ZaJMKb
Damian Przygodzki
1
@OliverDixon es el objeto predeterminado del evento React. reactjs.org/docs/events.html
Damian Przygodzki el
1
@DamianPrzygodzki Odio los elementos ocultos como este, una excelente manera de confundir a los desarrolladores.
Oliver Dixon el
1
@OliverDixon te siento, pero creo que es bueno acostumbrarse a que a veces hay algunos datos predeterminados aplicados al método, especialmente en eventos.
Damian Przygodzki
8

Su código debería funcionar perfectamente, lo uso de la misma manera. Solo asegúrese de que si el evento de clic se activa desde una pantalla emergente como un modal de arranque o algo así, el elemento creado debe estar dentro de ese modal, de lo contrario no se copiará. Siempre puede dar la identificación de un elemento dentro de ese modal (como un segundo parámetro) y recuperarlo con getElementById, luego agregar el elemento recién creado a ese en lugar del documento. Algo como esto:

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
fuente
8

Tomé un enfoque muy similar al de algunos de los anteriores, pero creo que lo hice un poco más concreto. Aquí, un componente principal pasará la url (o el texto que desee) como accesorio.

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
fuente
Esto fue útil porque quería tener una etiqueta de párrafo en lugar de Textarea
Ehsan Ahmadi
¡Gracias! El único problema es esconder el campo de texto
itshinkswhenitscold
3

Para aquellas personas que están tratando de seleccionar desde el DIV en lugar del campo de texto, aquí está el código. El código se explica por sí mismo, pero comente aquí si desea más información:

     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
fuente
3

Aquí hay otro caso de uso, si desea copiar la URL actual en su portapapeles:

Definir un método.

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

Llama a ese método

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

La mejor solución con ganchos de reacción, no necesita bibliotecas externas para eso

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>
)
}

consulte aquí para obtener más documentación sobre navigator.clipboard , documentación de navigator.clipboard navigotor.clipboard es compatible con una gran cantidad de navegadores.

Jaman-Dedy
fuente
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
fuente
1

Si desea seleccionar desde el DIV en lugar del campo de texto, aquí está el código. El "código" es el valor que debe copiarse.

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
fuente
1
La mejor práctica de SO es lograr su código con una explicación. Por favor hágalo.
MartenCatcher
0

Aquí está mi 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
fuente
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>
Beto
fuente
77
Agregue una explicación de cómo este código resuelve el problema, en lugar de solo publicar código.
Alexander van Oostenrijk
0

Encontré la mejor manera de hacerlo. Me refiero a la forma más rápida: w3school

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

Dentro de un componente funcional de reacción. Cree una función llamada 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>

</>

Si no usa React, w3schools también tiene una forma genial de hacerlo con información sobre herramientas incluida: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_copy_clipboard2

Si usa React, una buena idea para hacer: use un Toastify para alertar el mensaje. https://github.com/fkhadra/react-toastify Esta es la lib muy fácil de usar. Después de la instalación, puede cambiar esta línea:

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

Por algo como:

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

Si desea usarlo, no olvide instalar toastify. importar ToastContainer y también tostadas css:

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

y agregue el contenedor de tostadas dentro del 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
fuente
Su respuesta solo contiene la referencia a otro recurso, pero no una respuesta específica. Si el enlace w3schools es la solución correcta, escríbalo aquí.
f.khantsis