1

I am developing a React JS Web Application. I am new to react js. Now, I am trying to use Ref inside the stateless functional component to retrieve the input value. I followed some of the solutions I found online.

This is my component

const Login = (props) => {
  const {
    classes,
    width
  } = props;

  // Flip container to column on mobile screens.
  const panelDirection = width === 'xs' ? 'column' : 'row';

  let emailInput = null;
  let passwordInput = null;

  return (

    <Grid
      container
      direction="row"
      spacing={0}
      justify="center"
      alignItems="center"
      className={classes.background}
    >

      <Grid item sm={10} xs={12} className={scss.panel}>
      <form className="full-height" action="post">
        <Grid direction={panelDirection} container spacing={0}>
          <Grid
            item
            sm={6}
            xs={12}
          >
            <Card className={classNames(scss.card, classes['primary-card'])}>
              <CardContent className={scss['signup-content']}>
                <img src={logoImage} className={scss['signup-logo']} alt="logo" />
                <Typography variant="headline" component="h2" gutterBottom>
                  Web Portal
                </Typography>

              </CardContent>
              <CardActions>
                <Button fullWidth href="/register" color="secondary" variant="raised">Create an account</Button>
              </CardActions>
            </Card>
          </Grid>
          <Grid
            item
            sm={6}
            xs={12}
          >
            <Card className={scss.card}>
              <CardContent>
                <TextField
                  ref={(input) => { emailInput = input }}
                  label="Email Address"
                  fullWidth
                />
                <TextField
                  ref={(input) => { passwordInput = input }}
                  label="Password"
                  fullWidth
                  margin="normal"
                  type="password"
                />
              </CardContent>
              <CardActions className={scss['login-actions']}>
                <Button href="/login" color="primary" variant="raised">Login</Button>
                <Button href="/forgot-password">Forgot Password</Button>
              </CardActions>
            </Card>
          </Grid>
        </Grid>
        </form>
      </Grid>
    </Grid>

  );
};

As you can see, I am using ref to retrieve the values of email and password input fields. But, when I run, it is still giving me this error.

Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.

So, how can I fix my code? How can I use Ref correctly in the stateless function component?

Obviously, I followed this, How can I attach to a stateless component's ref in React?

I tried using the class as well. It is giving me the same error. This is the class version of my component.

class Login extends React.Component {



  submitForm = e => {
    e.preventDefault();
  }

  constructor(props)
  {
    super(props);
    this.emailInput = React.createRef();
    this.passwordInput = React.createRef();
  }


  render () {
    const { classes, width } = this.props;

    // Flip container to column on mobile screens.
    const panelDirection = width === 'xs' ? 'column' : 'row';
    return (

    <Grid
      container
      direction="row"
      spacing={0}
      justify="center"
      alignItems="center"
      className={classes.background}
    >

      <Grid item sm={10} xs={12} className={scss.panel}>
      <form className="full-height" action="post" onSubmit={this.submitForm}>
        <Grid direction={panelDirection} container spacing={0}>
          <Grid
            item
            sm={6}
            xs={12}
          >
            <Card className={classNames(scss.card, classes['primary-card'])}>
              <CardContent className={scss['signup-content']}>
                <img src={logoImage} className={scss['signup-logo']} alt="logo" />
                <Typography variant="headline" component="h2" gutterBottom>
                  Web Portal
                </Typography>

              </CardContent>
              <CardActions>
                <Button fullWidth href="/register" color="secondary" variant="raised">Create an account</Button>
              </CardActions>
            </Card>
          </Grid>
          <Grid
            item
            sm={6}
            xs={12}
          >
            <Card className={scss.card}>
              <CardContent>
                <TextField
                  ref={this.emailInput}
                  label="Email Address"
                  fullWidth
                />
                <TextField
                  ref={this.passwordInput}
                  label="Password"
                  fullWidth
                  margin="normal"
                  type="password"
                />
              </CardContent>
              <CardActions className={scss['login-actions']}>
                <Button type="submit" color="primary" variant="raised">Login</Button>
                <Button href="/forgot-password">Forgot Password</Button>
              </CardActions>
            </Card>
          </Grid>
        </Grid>
        </form>
      </Grid>
    </Grid>
    )
  }

}

Login.propTypes = {
  classes: PropTypes.shape({}).isRequired,
  width: PropTypes.string.isRequired
};

export default compose(withWidth(), withStyles(themeStyles, { withTheme: true }))(Login);
Wai Yan Hein
  • 13,651
  • 35
  • 180
  • 372

2 Answers2

2

If you insist in using stateless component (which to me they are great) you should use a callback to retrieve the value of your input:

// Login.js
const Login = (props) => {
  const {
    classes,
    width,
    onChange, // <- get the callback here
  } = props;

  ...

  return (
    ...
            <TextField
              name="email"
              onChange={onChange}
              label="Email Address"
              fullWidth
            />
            <TextField
              name="password"
              onChange={onChange}
              label="Password"
              fullWidth
              margin="normal"
              type="password"
            />
  ...
);


// Somewhere to include Login
class LoginPage extends Component {
  ...
  handleInputChange({ target }) {
    ...
    console.log(target.name, target.value);
  }
  render (
    <Login onChange={this.handleInputChange} ... />
  )
}


// Or connect it to Redux
const mapDispatchToProps = dispatch => {
  const updateLoginInputValues = ({ target }) => dispatch(updateLoginInputValues(target.name, target.value)));
  return {
    onChange: updateLoginInputValues,
  }
};
const connectedLogin = connect(null, mapDispatchToProps)(Login

The only part that you can improve is basically either handling the values by a state management or directly with React. Other than this you need to address the state at some point and you can't keep all of you components stateless.

Amin Paks
  • 276
  • 2
  • 15
1

State less component means it does not contains state, component only updates through props. So you can use class container for that. here is the solution...

import React, { Component } from "react";

class Login extends Component {
 constructor(props) {
  super(props);
  this.emailInput = React.createRef();
  this.passwordInput = React.createRef();
 }

 render() {
  const { classes, width } = this.props;

  // Flip container to column on mobile screens.
  const panelDirection = width === "xs" ? "column" : "row";

  return (
    <Grid container direction="row" spacing={0} justify="center" alignItems="center" className={classes.background}>
      <Grid item sm={10} xs={12} className={scss.panel}>
        <form className="full-height" action="post">
          <Grid direction={panelDirection} container spacing={0}>
            <Grid item sm={6} xs={12}>
              <Card className={classNames(scss.card, classes["primary-card"])}>
              <CardContent className={scss["signup-content"]}>
                <img src={logoImage} className={scss["signup-logo"]} alt="logo" />
                <Typography variant="headline" component="h2" gutterBottom>
                  Web Portal
                </Typography>
              </CardContent>
              <CardActions>
                <Button fullWidth href="/register" color="secondary" variant="raised">
                  Create an account
                </Button>
              </CardActions>
            </Card>
          </Grid>
          <Grid item sm={6} xs={12}>
            <Card className={scss.card}>
              <CardContent>
                <TextField ref={this.emailInput} label="Email Address" fullWidth />
                <TextField ref={this.passwordInput} label="Password" fullWidth margin="normal" type="password" />
              </CardContent>
              <CardActions className={scss["login-actions"]}>
                <Button href="/login" color="primary" variant="raised">
                  Login
                </Button>
                <Button href="/forgot-password">Forgot Password</Button>
              </CardActions>
            </Card>
          </Grid>
        </Grid>
      </form>
    </Grid>
  </Grid>
   )
 }
}

 export default Login;

Now you can get value of the textfields like this

this.emailInput.current.value and this.passwordInput.current.value

Herat Patel
  • 779
  • 3
  • 10