Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React refs with components

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>
    );
  }
});
like image 414
brandonhilkert Avatar asked Sep 19 '14 20:09

brandonhilkert


People also ask

Can you use ref on a React component?

In order to get a reference to a React component, you can either use this to get the current React component, or you can use a ref to get a reference to a component you own. They work like this: var MyComponent = React.

How do I add a ref to a React component?

Creating RefsRefs are created using React. createRef() and attached to React elements via the ref attribute. Refs are commonly assigned to an instance property when a component is constructed so they can be referenced throughout the component.

How do you use React ref in functional component?

To create a ref in a functional component we use the useRef() hook which returns a mutable object with a . current property set to the initialValue we passed to the hook. This returned object will persist for the full lifetime of the component. Thus, throughout all of its re-rendering and until it unmounts.

Can I use React createRef in functional component?

createRef can be used in both class and functional components.


2 Answers

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.

like image 77
Ross Allen Avatar answered Oct 21 '22 10:10

Ross Allen


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

like image 26
Michelle Tilley Avatar answered Oct 21 '22 09:10

Michelle Tilley