John Lamy
John Lamy

Reputation: 113

Using React's 'ref' attribute with Material-UI

I'm trying to access input data using React's "ref" attribute on a TextField in Material-UI. There doesn't seem to be an easy way of doing this via the 'inputRef' or 'inputProps'.

The below sample shows the use of inputProps on line 26. Assigning the name of the TextField to the 'ref' property does not appear to produce a valid object.

With the “inputRef”, which according the Material-ui docs forces the use of a function, attempting to pass the field data in as an attribute also doesn't work. Ex: (txt => this.name = txt)

Has anyone found a solution?

class MediaForm extends Component {
  constructor (props) {
    super(props)
    this.state = {}
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleSubmit (e) {
    const { title, colour } = this.refs
    e.preventDefault()
    window.alert(`New colour: ${title.value} ${colour.value}`)
  }

  render () {
    const { classes } = this.props
    return (
      <form className={classes.root}
        onSubmit={this.handleSubmit}>
        <div className={classes.field}>
          <TextField
            name='title'
            type='text'
            label='Title'
            placeholder='Movie title...'
            autoFocus
            inputProps={{ref: this.name}}
            required />
        </div>
        <div className={classes.field}>
          <Tooltip title='Add a colour the reflects the mood of the film'>
            <TextField
              name='colour'
              type='color'
              label='Mood'
              inputProps={{ref: this.name}}
              required />
          </Tooltip>
        </div>
        <Button
          variant='raised'
          color='primary'
          className={classes.button}>
          ADD
        </Button>
      </form>
    )
  }
}

MediaForm.propTypes = {
  classes: PropTypes.object.isRequired
}

export default withRoot(withStyles(styles)(MediaForm))

Upvotes: 11

Views: 29875

Answers (2)

trixn
trixn

Reputation: 16344

You do not need refs for that. The submit event contains the form as the target. You can access the inputs in a form via form.elements:

handleSubmit (event) {
    const { title, colour } = event.currentTarget.elements;
    event.preventDefault();
    window.alert(`New colour: ${title.value} ${colour.value}`);
}

To the problem with your refs: What does this.name refer to? You did not declare it anywhere so it is undefined. Passing undefined to the ref prop has no effect. Also how should it be possible to have two input refs being bound to the same instance property name. Are you aware that the this in your render function refers to the instance of your MediaForm component and therefore this.name is a property name on your component instance (which is undefined)?

If you want to obtain the individual refs for each input you should use the callback pattern. Note that String refs are deprecated and should not be used:

render() {
    return(
        <TextField
            name='title'
            type='text'
            label='Title'
            placeholder='Movie title...'
            autoFocus
            inputProps={{ref: input => this.titleInput = input}}
            required 
        />
    );
}

EDIT:

What you probably want is known as controlled component. In this pattern your parent component keeps track of the values of it's children (often inputs):

class MediaForm extends Component {
    constructor(props) {
        super(props);
        this.state = {
            title: '',
            colour: '',
        };
    }

    handleChange = event => {
        const {name, value} = event.currentTarget;
        this.setState({[name]: value});
    };

    handleSubmit = event => {
        event.preventDefault();
        const {title, colour} = this.state;
        window.alert(`New colour: ${title} ${colour}`);
    };

    render() {
        const {classes} = this.props;
        const {title, colour} = this.state;

        return (
            <form className={classes.root} onSubmit={this.handleSubmit}>
                <div className={classes.field}>
                    <TextField
                        name="title"
                        type="text"
                        value={title}
                        onChange={this.handleChange}
                        label="Title"
                        placeholder="Movie title..."
                        required
                    />
                </div>
                <div className={classes.field}>
                    <Tooltip title="Add a colour the reflects the mood of the film">
                        <TextField
                            name="colour"
                            type="color"
                            value={colour}
                            onChange={this.handleChange}
                            label="Mood"
                            required
                        />
                    </Tooltip>
                </div>
                <Button
                    type="submit"
                    variant="raised"
                    color="primary"
                    className={classes.button}
                >
                    ADD
                </Button>
            </form>
        );
    }
}

Edit kxm42q76vv

Now your parent has full control and access over the value of each input via this.state.title and this.state.colour. Also no need for any ref here.

Upvotes: 7

challenger
challenger

Reputation: 2214

class MediaForm extends Component {
  refs = {}  // <____ notice this
  constructor (props) {
    super(props)
    this.state = {}
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleSubmit (e) {
    const { title, colour } = this.refs
    e.preventDefault()
    window.alert(`New colour: ${title.value} ${colour.value}`)
  }

  render () {
    const { classes } = this.props
    return (
      <form className={classes.root}
        onSubmit={this.handleSubmit}>
        <div className={classes.field}>
          <TextField
            inputProps={{ref => this.refs.title = ref}}
            name='title'
            type='text'
            label='Title'
            placeholder='Movie title...'
            autoFocus
            required />
        </div>
        <div className={classes.field}>
          <Tooltip title='Add a colour the reflects the mood of the film'>
            <TextField
              name='colour'
              inputProps={{ref => this.refs.colour = ref}}
              type='color'
              label='Mood'
              required />
          </Tooltip>
        </div>
        <Button
          variant='raised'
          color='primary'
          className={classes.button}>
          ADD
        </Button>
      </form>
    )
  }
}

MediaForm.propTypes = {
  classes: PropTypes.object.isRequired
}

export default withRoot(withStyles(styles)(MediaForm))

Upvotes: 0

Related Questions