5

I have a ReactJS component with an HTML <textarea> inside.

The <textarea> has the value attribute pre-populated (see below ... I'm generating a link for the user to copy).

I want to automatically select the <textarea> content so it'll be more convenient for the user to copy the text. I'm trying to do this in the React componentDidMount call. It works... sort of.

As near as I can tell, the component is mounting so slowly that the process is still ongoing even when componentDidMount is already being called. The reason I say this is: if I call myTextarea.select() directly within componentDidMount, it fails 100% of the time. However, if I put .select() in a setTimeout(...) call so it waits a bit before trying to select the content, it works 100% of the time.

Edit/note: after poking around a little more, I've discovered some sentiment indicating that using setTimeout(...) for this purpose is poor practice, so now I'm even more eager to avoid using it.

What might I be missing, and is there a better way to accomplish this?

Here's what I have.

This version fails.

var GenerateLinkUi = React.createClass({
  componentDidMount: function() {
    document.getElementById('cyoag-generated-link-textarea').select();
  },
  render: function () {
    return (
      <div id='cyoag-generate-link-ui'>
        <textarea readOnly id='cyoag-generated-link-textarea' value={config.hostDomain + 'link?id=' + this.props.nodeUid} />
      </div>
    );
  }
});

This version succeeds.

var GenerateLinkUi = React.createClass({
  componentDidMount: function() {
    setTimeout(function(){
      document.getElementById('cyoag-generated-link-textarea').select();
    }, 500);
  },
  render: function () {
    return (
      <div id='cyoag-generate-link-ui'>
        <textarea readOnly id='cyoag-generated-link-textarea' value={config.hostDomain + 'link?id=' + this.props.nodeUid} />
      </div>
    );
  }
});

Edit: this was flagged as a duplicate. The linked question raises more questions for me, and does not provide a clear answer, as discussed in comments below. If ref executes after the component mounts, and componentDidMount also executes "after" component mounting, I struggle to understand the difference between the two, beyond the fact that ref is passed a DOM element as an arg; where in componentDidMount I'd have to use document.getElementById or something. I'd appreciate if anyone could describe deeper differences between the two.

In the meantime I've found other workarounds for this issue but I'm still eager to learn more on the subject.

Steverino
  • 2,099
  • 6
  • 26
  • 50
  • Possible duplicate of [React set focus on input after render](http://stackoverflow.com/questions/28889826/react-set-focus-on-input-after-render) – zurfyx Jan 24 '17 at 08:45

2 Answers2

1

I'll take a guess at what's happening.

Does the component get rendered after componentDidMount again? I imagine it will render the textarea again and overwrite the select() because you have done it on the actual DOM and not the virtual.

It works when using setTimeout because you do it after all of the renders that occurred after componentDidMount. I imagine if you updated the props again it would overwrite the select() again.

Use refs to solve this.

var GenerateLinkUi = React.createClass({
  componentDidMount: function() {
    this.textArea.select();
  },
  setTextArea: function(ref) {
    this.textArea = ref;
  }
  render: function () {
    return (
      <div id='cyoag-generate-link-ui'>
        <textarea ref={this.setTextArea} readOnly id='cyoag-generated-link-textarea' value={config.hostDomain + 'link?id=' + this.props.nodeUid} />
      </div>
    );
  }
});
Martin Dawson
  • 7,455
  • 6
  • 49
  • 92
  • I found a workaround in the meantime; but I'm trying to understand this answer because seems on the right track. If there are subsequent renders being triggered, I can't find what's causing. I suppose it's possible. My question then is, if `ref` executes after the component mounts, and `componentDidMount` also executes after component mounting, I struggle to understand the difference between the two, other than the fact that `ref` is passed a DOM element as an arg; where in `componentDidMount` I'd have to use `document.getElementById` or something. – Steverino Jan 24 '17 at 09:32
  • 1
    @Steverino The difference between using `ref` and `document.getElementById` is that React can see the changes you make on the `ref` because it is modifying the virtual DOM that is rendered. React can't see the changes you make on the actual DOM so it overwrites it with the virtual DOM which doesn't have the `select()` on it. Remember the `` that you render is a virtual DOM element and different to the actual DOM element rendered on the page. – Martin Dawson Jan 24 '17 at 10:29
1

The technique to do so is called refs callback.

React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.

BTW, you are using the correct componentDidMount method.


Refs are there for you

enter image description here

It is present in the ReactClass.

prosti
  • 42,291
  • 14
  • 186
  • 151