diegoaguilar
diegoaguilar

Reputation: 8376

How to properly set references to React components?

Using React with react-redux, I'm trying to get this login form:

import React from 'react'
import RaisedButton from 'material-ui/lib/raised-button';
import TextField from 'material-ui/lib/text-field';
const LoginButton = () => (
  <form>
    <div>
      <TextField hint="E-mail" floatingLabelText="E-mail" ref="emailInput"></TextField><br/>
    </div>
    <div>
      <TextField hint="Contraseña" floatingLabelText="Contraseña" type="password" ref="passwordInput"></TextField><br/><br/>
    </div>
    <div>
      <RaisedButton label="Acceder" primary={true} onClick={e => {
          console.log(emailInput.value);
        }}/>
    </div>
  </form>
);

export default LoginButton;

Which, at the same time it's being used as:

import React from 'react'
import Card from 'material-ui/lib/card/card';
import CardActions from 'material-ui/lib/card/card-actions';
import CardHeader from 'material-ui/lib/card/card-header';
import CardText from 'material-ui/lib/card/card-text';
import CardTitle from 'material-ui/lib/card/card-title';
import RaisedButton from 'material-ui/lib/raised-button';

import Paper from 'material-ui/lib/paper';
import { Link } from 'react-router'
import LoginForm from '../containers/LoginForm'

const divStyle = {
  marginTop: '100px'
};

const paperStyle = {
  padding: '25px'
}

const Login = () => (
  <div className="row center-md" style={divStyle}>
    <div className="col-md-6">
      <div className="box">
        <Paper zDepth={4} style={paperStyle}>
          <div>
            <h1>Acceder</h1>
          </div>
          <LoginForm />
        </Paper>
      </div>
    </div>
  </div>
);

export default Login;

So running this I'm getting a:

Uncaught Invariant Violation: Stateless function components cannot have refs.

So I have some questions, how should I fix this error? What's the magic allowing me to export components like this instead of exporting with React.createClass (I've copied this pattern from a couple of react/redux examples)?

Upvotes: 3

Views: 4753

Answers (3)

user6710053
user6710053

Reputation: 51

In the stateless component you can use such approach:

const TextFieldSubmit = (props) => {
  let input;

  return (
    <div className='ui input'>
      <input
        ref={node => input = node}
        type='text'
      >
      </input>
      <button
        onClick={() => {
          props.onSubmit(input.value);
          input.value = '';
        }}
        className='ui primary button'
        type='submit'
      >
        Submit
      </button>
    </div>
  );
};

For more details please go: https://www.fullstackreact.com/p/using-presentational-and-container-components-with-redux/

Upvotes: 5

tl;dr

You are using a Stateless Component, the ref property can't be used with this type of component.

Long answer

Stateless components can be seen as "static html", they only have the props given to it as the first argument of the function.

They can't have state, refs or lifecycle methods.

You need to create a React.Component to be able to use the ref property. You can create this component in two ways:

If you are not using ES6 or you need to use the mixins property you should do:
var React = require("react");

module.exports = React.createClass({
  render() {
    return (
      <form>
        <div>
          <TextField hint="E-mail" floatingLabelText="E-mail" ref="emailInput"></TextField><br/>
        </div>
        <div>
          <TextField hint="Contraseña" floatingLabelText="Contraseña" type="password" ref="passwordInput"></TextField><br/><br/>
        </div>
        <div>
          <RaisedButton label="Acceder" primary={true} onClick={e => {
              console.log(emailInput.value);
            }}/>
        </div>
      </form>
    );
  }
});
and if you are using ES6
import React, { Component } from "react"

export default class Form extends Component {
  render() {
    return (
      <form>
        <div>
          <TextField hint="E-mail" floatingLabelText="E-mail" ref="emailInput"></TextField><br/>
        </div>
        <div>
          <TextField hint="Contraseña" floatingLabelText="Contraseña" type="password" ref="passwordInput"></TextField><br/><br/>
        </div>
        <div>
          <RaisedButton label="Acceder" primary={true} onClick={e => {
              console.log(emailInput.value);
            }}/>
        </div>
      </form>
    );
  }
}

Upvotes: 3

Rick Runyon
Rick Runyon

Reputation: 4354

Those components are defined using the stateless function component syntax provided by React. From the React docs:

This simplified component API is intended for components that are pure functions of their props. These components must not retain internal state, do not have backing instances, and do not have the component lifecycle methods. They are pure functional transforms of their input, with zero boilerplate. However, you may still specify .propTypes and .defaultProps by setting them as properties on the function, just as you would set them on an ES6 class.

The problem you're running into is described here:

NOTE: Because stateless functions don't have a backing instance, you can't attach a ref to a stateless function component. Normally this isn't an issue, since stateless functions do not provide an imperative API. Without an imperative API, there isn't much you could do with an instance anyway. However, if a user wants to find the DOM node of a stateless function component, they must wrap the component in a stateful component (eg. ES6 class component) and attach the ref to the stateful wrapper component.

If you need this ref, either change your LoginButton component to use the standard class LoginButton extends React.Component component declaration syntax, or wrap your stateless function component in a stateful component.

Read more here.

Upvotes: 2

Related Questions