Clipboard.js: Usage with React (just an example)

Created on 1 Jun 2016  路  12Comments  路  Source: zenorocha/clipboard.js

Description

First of all, thanks and congrats for _clipboard.js_, it is a big enhancement for every web developer.

I am using _clipboard.js_ with React and it works. I would like to add style from material-ui.

At first I was using the data-clipboard-target attribute, then I noticed the last part of documentation that explaing how to set target dynamically.

So I solved my issue while I was opening it, just reading the docs. I just want to report my working example, to give back to the community.

Minimal example

import React from 'react'

class ResultURL extends React.Component {
  componentDidMount () {
    const {
      buttonId,
      inputId
    } = this.props

    const buttonIdSelector = `#${buttonId}`

    const Clipboard = this.props.clipboard

    this.clipboard = new Clipboard(
      buttonIdSelector, {
        target: () => document.getElementById(inputId)
      }
    )
  }

  render () {
    const {
      buttonId,
      inputId,
      value
    } = this.props

    const inputIdSelector = `#${inputId}`

    return (
      <div>
        <input
          id={inputId}
          type={'text'}
          value={value}
          readOnly
        />
        <button
          id={buttonId}
        > Copy
        </button>
      </div>
    )
  }
}

ResultURL.propTypes = {
  clipboard: React.PropTypes.func.isRequired,
  value: React.PropTypes.string,
  inputId: React.PropTypes.string.isRequired,
  buttonId: React.PropTypes.string.isRequired
}

export default ResultURL

Most helpful comment

Done: https://github.com/zenorocha/clipboard.js/wiki/Guides ;)

Thanks @fibo for sharing your experience!

All 12 comments

Where this this.props.clipboard comes from?

Can you post an example working on JSFiddle or CodePen?

Thanks

It is just

import Clipboard from 'clipboard'

passed as a React prop.

It is worth putting this in the Wiki.

@brunogenaro I wrote a Codepen. The input text is not editable cause you need to pass an onChange prop that modifies the state, but the Clipboard implementation works.

See the Pen Clipboard.js + React by fibo (@fibo) on CodePen.

Done: https://github.com/zenorocha/clipboard.js/wiki/Guides ;)

Thanks @fibo for sharing your experience!

I had a hard time destroy()-ing my clipboard object at componentWillUnmount but finally was able to do it with this.clipboard instance. In my Meteor app, not destroying it when component unmounts results to x times action is made with x as the number of times Clipboard instance is instantiated, in my case, at componentDidMount.

Perhaps adding that declaration to React guide would help others. it's as simple as:

componentWillUnmount() {
    this.clipboard.destroy();
}

Cheers!

@jayjaydluffy thanks

I added it to the codepen http://codepen.io/fibo/pen/wWKGNK?editors=0010 as

  componentWillUnmount() {
    this.props.clipboard.destroy()
  }

I also used React ref which is much cleaner.

@fibo Hello, thanks for the react example.
I try your implementation in my project, instead of <input> and <button>, I use the material-ui react component <TextField> and <FlatButton> .

import TextField from 'material-ui/TextField' import FlatButton from 'material-ui/FlatButton' import Clipboard from 'clipboard'

````
componentDidMount() {
const copyButton = this.copyButton
const passwordTextField = this.passwordTextField

this.clipboard = new Clipboard(
  copyButton, {
    target: () => passwordTextField
  }
)

}
````

in render()
<TextField disabled={true} ref={(input) => { console.info(input) return this.passwordTextField = input }} hintText='Password' style={style.textField.field} fullWidth={true} underlineShow={false} value={this.state.generatedPassword} /> <FlatButton style={style.generateButton} primary={true} label="Generate" onClick={this.handleGeneratePassword} /> <FlatButton style={style.copyButton} ref={(element) => { console.info(element) return this.copyButton = element }} disabled={copyButtonDisabled} primary={true} label="Copy" />

and I get this error

frontend.bbd2615c67e904c490d0.js:1 Uncaught TypeError: First argument must be a String, HTMLElement, HTMLCollection, or NodeList at r (frontend.bbd2615c67e904c490d0.js:1) at t.value (frontend.bbd2615c67e904c490d0.js:1) at new t (frontend.bbd2615c67e904c490d0.js:1) at t.value (frontend.bbd2615c67e904c490d0.js:1) at vendor.bbd2615c67e904c490d0.js:28 at l (vendor.bbd2615c67e904c490d0.js:28) at vendor.bbd2615c67e904c490d0.js:28 at e.notifyAll (vendor.bbd2615c67e904c490d0.js:23) at o.close (vendor.bbd2615c67e904c490d0.js:28) at o.closeAll (vendor.bbd2615c67e904c490d0.js:9) r @ frontend.bbd2615c67e904c490d0.js:1 value @ frontend.bbd2615c67e904c490d0.js:1 t @ frontend.bbd2615c67e904c490d0.js:1 value @ frontend.bbd2615c67e904c490d0.js:1 (anonymous) @ vendor.bbd2615c67e904c490d0.js:28 l @ vendor.bbd2615c67e904c490d0.js:28 (anonymous) @ vendor.bbd2615c67e904c490d0.js:28 e.notifyAll @ vendor.bbd2615c67e904c490d0.js:23 close @ vendor.bbd2615c67e904c490d0.js:28 closeAll @ vendor.bbd2615c67e904c490d0.js:9 perform @ vendor.bbd2615c67e904c490d0.js:9 l @ vendor.bbd2615c67e904c490d0.js:23 perform @ vendor.bbd2615c67e904c490d0.js:9 batchedUpdates @ vendor.bbd2615c67e904c490d0.js:28 a @ vendor.bbd2615c67e904c490d0.js:9 _renderNewRootComponent @ vendor.bbd2615c67e904c490d0.js:23 _renderSubtreeIntoContainer @ vendor.bbd2615c67e904c490d0.js:23 render @ vendor.bbd2615c67e904c490d0.js:23 324 @ frontend.bbd2615c67e904c490d0.js:1 t @ vendor.bbd2615c67e904c490d0.js:1 window.webpackJsonp @ vendor.bbd2615c67e904c490d0.js:1 (anonymous) @ frontend.bbd2615c67e904c490d0.js:1

I understand that the first argument of Clipboard is not string or html element, but I don't know how to pass it from material-ui react component.

Do you have same example to show how to use Material-ui component with clipboard please ?

Thanks a lot

Hi @leialexisjiang, I have no example or snippet already working with material ui. It looks like TextField and FlatButton do not support ref props that is available, as far as I know, in builtin React components.

@fibo Shouldn't the example be invoking destroy on this.clipboard (as opposed to this.props.clipboard)?

@Clee681 Yes it makes more sense, codepen updated, 10x

Facing the same issue @leialexisjiang is facing. I use Material UI copy Icon as button

import Clipboard from 'clipboard';
import IconCopy from '@material-ui/icons/FileCopy';

class CopyClass extends React.Component {

  componentDidMount () {
    const button = this.copyIcon
    const input = this.email

    this.clipboard = new Clipboard(
      button, {
        target: () => input
      }
    )
  }

  componentWillUnmount() {
 聽 聽this.clipboard.destroy()
  } 

  renderUserCaption () {
    return (
      <div>
        <input 
          ref={el => this.email = el} 
          value={this.props.user.email_primary}
          type="text"
          readOnly={true}
        />
        {document.queryCommandSupported('copy') && canCopy && (
          <Tooltip title="Copy this email address">
            <IconCopy 
              ref={(el) => this.copyIcon = el}
            />
          </Tooltip>
        )}
      </div>
    )
  }

  render () {
    return (
      <div>
        <SomeOtherComponent
          titleCaption={this.renderUserCaption()}
        >
          {this.props.children}
        </SomeOtherComponent>
      </div>
    )
  }
}

I am getting the this error

clipboard.js:736 Uncaught TypeError: First argument must be a String, HTMLElement, HTMLCollection, or NodeList
    at listen (clipboard.js:736)
    at Clipboard.listenClick (clipboard.js:183)
    at new Clipboard (clipboard.js:151)
    at UserCard.componentDidMount (index.js:107)
    at commitLifeCycles (react-dom.development.js:16227)
    at commitAllLifeCycles (react-dom.development.js:17592)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at commitRoot (react-dom.development.js:17788)
    at completeRoot (react-dom.development.js:19240)
    at performWorkOnRoot (react-dom.development.js:19169)
    at performWork (react-dom.development.js:19077)
    at performSyncWork (react-dom.development.js:19051)
    at requestWork (react-dom.development.js:18920)
    at scheduleWork (react-dom.development.js:18729)
    at Object.enqueueSetState (react-dom.development.js:12457)
    at AsyncComponent../node_modules/react/cjs/react.development.js.Component.setState (react.development.js:375)
    at AsyncComponent._callee$ (index.js:20)
    at tryCatch (runtime.js:62)
    at Generator.invoke [as _invoke] (runtime.js:296)
    at Generator.prototype.(anonymous function) [as next] (http://localhost:4000/static/js/bundle.js:17522:21)
    at step (index.js:79)
    at index.js:79
listen @ clipboard.js:736
listenClick @ clipboard.js:183
Clipboard @ clipboard.js:151
componentDidMount @ index.js:107
commitLifeCycles @ react-dom.development.js:16227
commitAllLifeCycles @ react-dom.development.js:17592
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
commitRoot @ react-dom.development.js:17788
completeRoot @ react-dom.development.js:19240
performWorkOnRoot @ react-dom.development.js:19169
performWork @ react-dom.development.js:19077
performSyncWork @ react-dom.development.js:19051
requestWork @ react-dom.development.js:18920
scheduleWork @ react-dom.development.js:18729
enqueueSetState @ react-dom.development.js:12457
./node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:375
_callee$ @ index.js:20
tryCatch @ runtime.js:62
invoke @ runtime.js:296
prototype.(anonymous function) @ runtime.js:114
step @ index.js:79
(anonymous) @ index.js:79
Promise.then (async)
step @ index.js:79
(anonymous) @ index.js:79
(anonymous) @ index.js:79
componentDidMount @ index.js:9
commitLifeCycles @ react-dom.development.js:16227
commitAllLifeCycles @ react-dom.development.js:17592
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
commitRoot @ react-dom.development.js:17788
completeRoot @ react-dom.development.js:19240
performWorkOnRoot @ react-dom.development.js:19169
performWork @ react-dom.development.js:19077
performSyncWork @ react-dom.development.js:19051
requestWork @ react-dom.development.js:18920
scheduleWork @ react-dom.development.js:18729
enqueueSetState @ react-dom.development.js:12457
./node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:375
onStateChange @ connectAdvanced.js:205
dispatch @ redux.js:214
(anonymous) @ middleware.js:13
(anonymous) @ index.js:11
dispatch @ redux.js:611
(anonymous) @ index.js:128
Promise.then (async)
(anonymous) @ index.js:125
(anonymous) @ index.js:121
(anonymous) @ index.js:8
(anonymous) @ redux.js:462
componentDidMount @ index.js:16
commitLifeCycles @ react-dom.development.js:16227
commitAllLifeCycles @ react-dom.development.js:17592
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
commitRoot @ react-dom.development.js:17788
completeRoot @ react-dom.development.js:19240
performWorkOnRoot @ react-dom.development.js:19169
performWork @ react-dom.development.js:19077
performSyncWork @ react-dom.development.js:19051
requestWork @ react-dom.development.js:18920
scheduleWork @ react-dom.development.js:18729
scheduleRootUpdate @ react-dom.development.js:19397
updateContainerAtExpirationTime @ react-dom.development.js:19425
updateContainer @ react-dom.development.js:19482
./node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render @ react-dom.development.js:19774
(anonymous) @ react-dom.development.js:19914
unbatchedUpdates @ react-dom.development.js:19280
legacyRenderSubtreeIntoContainer @ react-dom.development.js:19910
render @ react-dom.development.js:19971
./src/index.js @ index.js:21
__webpack_require__ @ bootstrap c0f3913402f91ebe9253:707
fn @ bootstrap c0f3913402f91ebe9253:112
0 @ index.js:21
__webpack_require__ @ bootstrap c0f3913402f91ebe9253:707
./node_modules/@babel/runtime/helpers/arrayWithoutHoles.js.module.exports @ bootstrap c0f3913402f91ebe9253:805
(anonymous) @ bootstrap c0f3913402f91ebe9253:805
index.js:115 Uncaught TypeError: Cannot read property 'destroy' of undefined
    at UserCard.componentWillUnmount (index.js:115)
    at callComponentWillUnmountWithTimer (react-dom.development.js:16065)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at safelyCallComponentWillUnmount (react-dom.development.js:16072)
    at commitUnmount (react-dom.development.js:16446)
    at commitNestedUnmounts (react-dom.development.js:16477)
    at unmountHostComponents (react-dom.development.js:16738)
    at commitDeletion (react-dom.development.js:16790)
    at commitAllHostEffects (react-dom.development.js:17544)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at commitRoot (react-dom.development.js:17753)
    at completeRoot (react-dom.development.js:19240)
    at performWorkOnRoot (react-dom.development.js:19169)
    at performWork (react-dom.development.js:19077)
    at performSyncWork (react-dom.development.js:19051)
    at requestWork (react-dom.development.js:18920)
    at scheduleWork (react-dom.development.js:18729)
    at Object.enqueueSetState (react-dom.development.js:12457)
    at AsyncComponent../node_modules/react/cjs/react.development.js.Component.setState (react.development.js:375)
    at AsyncComponent._callee$ (index.js:20)
    at tryCatch (runtime.js:62)
    at Generator.invoke [as _invoke] (runtime.js:296)
    at Generator.prototype.(anonymous function) [as next] (http://localhost:4000/static/js/bundle.js:17522:21)
    at step (index.js:79)
    at index.js:79

Can someone tell me where I have done wrong?

Was this page helpful?
0 / 5 - 0 ratings