17

I have React form that has a Component used to render a drop down because the options are coming from an API. However, I can't access the ref for the embedded component. I'm putting together my first form and trying to understand the best way to approach this.

var ActivityForm = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();

    var noteCategoryId = this.refs.note_category_id.getDOMNode().value.trim();
    var content = this.refs.content.getDOMNode().value.trim();

    if (!category || !content) {
      return;
    }

    // this.props.onCommentSubmit({author: author, text: text});

    this.refs.note_category_id.getDOMNode().value = '';
    this.refs.content.getDOMNode().value = '';
    return;
  },
  render: function() {
    return (
      <div className="new-activity">
        <h3>New Activity</h3>
        <form onSubmit={this.handleSubmit}>
          <textarea ref='content' />
          <br />

          <label>Category</label>
          <ActivityFormCategoryDropdown /> # THE REF IN THIS COMPONENT ISN'T ACCESSIBLE
          <br />

          <input type="submit" value="Add Activity" />
        </form>
      </div>
    );
  }
});
brandonhilkert
  • 4,205
  • 5
  • 25
  • 38

2 Answers2

25

It is preferred to treat the ref as a callback attribute and no longer depend on the refs Object. If you do use the refs Object, avoid accessing refs of descendant components. You should treat refs as a private accessor and not part of a component's API. Treat only the methods exposed on a component instance as its public API.

For this case, I suggest grabbing the form from the submit event and traversing its child form elements as needed. Add name attributes since that's how form elements are identified in standard form submissions, and then you shouldn't need refs at all:

var ActivityForm = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();
    var form = e.target;

    // Use the standard [`HTMLFormElement.elements`][1] collection
    //
    // [1]: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements
    var content = form.elements['content'].value;

    // do more things with other named form elements
  },
  render: function() {
    return (
      <div className="new-activity">
        <h3>New Activity</h3>
        <form onSubmit={this.handleSubmit}>
          <textarea name='content' />
          <br />

          <label>Category</label>
          <ActivityFormCategoryDropdown />
          <br />

          <input type="submit" value="Add Activity" />
        </form>
      </div>
    );
  }
});

Update 2016-09-21: Revise suggestion to avoid the refs Object all together per guidance from the ref String Attribute docs.

Ross Allen
  • 43,772
  • 14
  • 97
  • 95
  • 2
    Side note - This line: `var form = e.target.getDOMNode();` should be `var form = e.target;` – brandonhilkert Sep 19 '14 at 21:22
  • 1
    Negative points for using query selector, if the concern is refs use onChange. – dirkdig Dec 31 '16 at 16:10
  • @dirkdig Agreed about `querySelector`. This is an old answer, and I'm not sure why I thought that was a good idea. – Ross Allen Jan 01 '17 at 23:12
  • @RossAllen , perhaps you can inform me then. When is it acceptable to use refs? – dirkdig Jan 03 '17 at 01:43
  • 1
    @dirkdig There's no simple answer for "when to use refs," but there is often a more maintainable way to reference components than via refs. http://stackoverflow.com/a/29504636/368697 is a good starting point to read and React's documentation on [Refs and the DOM](https://facebook.github.io/react/docs/refs-and-the-dom.html) is worthwhile as well. – Ross Allen Jan 04 '17 at 19:46
  • Another crucial point is to know when to use ref and when to avoid it. [This article](https://www.robinwieruch.de/react-ref-attribute-dom-node/) summarizes the use cases pretty well and showcases the ref attribute again. – Robin Wieruch Sep 29 '17 at 15:04
20

Composite components can have their own refs; you can reach in to them to access the refs further down the component hierarchy.

Example:

<script src="http://fb.me/react-with-addons-0.11.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.11.2.js"></script>

<div id="app"></div>

<script type="text/jsx">
/** @jsx React.DOM */

var Parent = React.createClass({
  render: function() {
    return (
      <div>
        <Child ref="child" />
        <div><button onClick={this.handleClick}>Alert Text</button></div>
      </div>
    );
  },
  
  handleClick: function() {
    alert(this.refs.child.refs.textarea.getDOMNode().value);
  }
});

var Child = React.createClass({
  render: function() {
    return <textarea ref="textarea" />;
  }
});

React.renderComponent(<Parent />, document.getElementById("app"));
</script>

However, ssorallen is correct—you should try to avoid this if possible. Instead, you should either pass callbacks into children:

<script src="http://fb.me/react-with-addons-0.11.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.11.2.js"></script>

<div id="app"></div>

<script type="text/jsx">
/** @jsx React.DOM */

var Parent = React.createClass({
  getInitialState: function() {
    return { text: "" };
  },

  render: function() {
    return (
      <div>
        <Child onChange={this.updateText} />
        <div><button onClick={this.handleClick}>Alert Text</button></div>
      </div>
    );
  },
  
  handleClick: function() {
    alert(this.state.text);
  },

  updateText: function(text) {
    this.setState({text: text});
  }
});

var Child = React.createClass({
  render: function() {
    return <textarea onChange={this.handleChange} />;
  },

  handleChange: function(evt) {
    this.props.onChange(evt.target.value);
  }
});

React.renderComponent(<Parent />, document.getElementById("app"));
</script>

or expose a public API in the child:

<script src="http://fb.me/react-with-addons-0.11.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.11.2.js"></script>

<div id="app"></div>

<script type="text/jsx">
/** @jsx React.DOM */

var Parent = React.createClass({
  render: function() {
    return (
      <div>
        <Child ref="child" />
        <div><button onClick={this.handleClick}>Alert Text</button></div>
      </div>
    );
  },
  
  handleClick: function() {
    alert(this.refs.child.getText());
  }
});

var Child = React.createClass({
  render: function() {
    return <textarea />;
  },

  getText: function() {
    return this.getDOMNode().value;
  }
});

React.renderComponent(<Parent />, document.getElementById("app"));
</script>

or use some other data flow management (e.g. flux, events, etc).

Michelle Tilley
  • 157,729
  • 40
  • 374
  • 311
  • The "managed" form element approach (your top example) is great albeit a larger upfront cost. – Ross Allen Sep 19 '14 at 20:40
  • Hi, I want to know which way is better(first one I think) and why. Actually, why we want to avoid ref and use state. I see many posts on SO say that but I do not understand perfectly. Thank you. – MichaelMao Apr 07 '16 at 16:38
  • Now that the preferred value for the `ref` attribute is a callback (see the [`ref` Callback Attribute docs](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute)), it's misleading to show an example of accessing descendants' `refs`. That will likely break in a future version of React. – Ross Allen Sep 21 '16 at 17:17