Material-ui: Select onChange target doesn't return an html tag

Created on 29 Sep 2017  路  17Comments  路  Source: mui-org/material-ui

  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior

Get the input html tag under select

Current Behavior

Get only an object with value field

Steps to Reproduce (for bugs)

Try to render MySelect from

const setData = (e) => console.log(e.target)
export const MySelect = () => (
    <FormControl fullWidth>
        <InputLabel htmlFor="age-simple">Civilit茅</InputLabel>
        <Select
            value={'Monsieur'}
            onChange={setData}
            input={<Input />}
            label={'Civilit茅'}
            name='civility'
        >
            <MenuItem value={'Mr'}>Monsieur</MenuItem>
            <MenuItem value={'Mme'}>Madame</MenuItem>
        </Select>
    </FormControl>
)
  1. Look at your console

Context

I need to get the name field of my Select
However this one is not present in the triggered event

Your Environment

Ubuntu 17.04 (64bit)

| Tech | Version |
|--------------|---------|
| Material-UI | 1.0.0-beta.12 |
| React | 15.6.1 |
| browser | Chrome 61.0.3163.100 (64bit) |
| node | 8.1.4 |
| npm | 5.4.2聽|
| create-react-app聽| 1.4.0聽|

question

Most helpful comment

Here's an example for the TextField component:

import React from "react";
import PropTypes from "prop-types";
import { TextField } from "material-ui";

const MaterialInput = ({
  field,
  form: { touched, errors },
  label,
  ...props
}) => (
  <TextField
    {...props}
    {...field}
    value={field.value || ""}
    error={Boolean(touched[field.name] && errors[field.name])}
    label={(touched[field.name] && errors[field.name]) || label}
  />
);

MaterialInput.propTypes = {
  field: PropTypes.shape({
    name: PropTypes.string,
    value: PropTypes.any,
    onChange: PropTypes.func,
    onBlur: PropTypes.func
  }).isRequired,
  form: PropTypes.shape({
    touched: PropTypes.object,
    errors: PropTypes.object
    // the rest of the formik bag too
  }).isRequired,
  label: PropTypes.string.isRequired
};

export default MaterialInput;

Here's an example with Selects:

import React from "react";
import PropTypes from "prop-types";
import { TextField } from "material-ui";

const MaterialInputSelect = ({
  field,
  form: { touched, errors },
  label,
  options,
  ...props
}) => (
  <TextField
    {...props}
    {...field}
    value={field.value || ""}
    error={Boolean(touched[field.name] && errors[field.name])}
    label={(touched[field.name] && errors[field.name]) || label}
    select
    SelectProps={{ native: true }}
  >
    <option value="">Select</option>
    {options.map(option => (
      <option key={option.value} value={option.value}>
        {option.label}
      </option>
    ))}
  </TextField>
);

MaterialInputSelect.propTypes = {
  field: PropTypes.shape({
    name: PropTypes.string,
    value: PropTypes.any,
    onChange: PropTypes.func,
    onBlur: PropTypes.func
  }).isRequired,
  form: PropTypes.shape({
    touched: PropTypes.object,
    errors: PropTypes.object
    // the rest of the formik bag too
  }).isRequired,
  label: PropTypes.string.isRequired,
  options: PropTypes.arrayOf(
    PropTypes.shape({
      value: PropTypes.any,
      label: PropTypes.string
    })
  )
};

MaterialInputSelect.defaultProps = {
  options: []
};

export default MaterialInputSelect;

And here's an example with Switches:

import React from "react";
import PropTypes from "prop-types";
import { FormControlLabel, Switch } from "material-ui";

const MaterialInput = ({
  field: { value, ...field },
  form,
  label,
  ...props
}) => (
  <FormControlLabel
    control={<Switch {...field} {...props} checked={value || false} />}
    label={label}
  />
);

MaterialInput.propTypes = {
  field: PropTypes.shape({
    name: PropTypes.string,
    value: PropTypes.any,
    onChange: PropTypes.func,
    onBlur: PropTypes.func
  }).isRequired,
  form: PropTypes.shape({
    // the formik bag
  }).isRequired,
  label: PropTypes.string.isRequired
};

export default MaterialInput;

All 17 comments

How about reproducing with the codesandbox link than was in the issue template?

@rosskevin This time, the provided information is enough to look into the issue.

I need to get the name field of my Select
However this one is not present in the triggered event

@robininfo-edsx The event is coming from a click/key interaction as you can find in:
https://github.com/callemall/material-ui/blob/6918de85cc2fc5a05ae400cc21f4d4319eae195a/src/Select/SelectInput.js#L149

You won't be able to access the hidden input field this way. This is not something we want to support. But then, I don't understand the use case as you are the one providing a name to the input.

Anyway, you can use the inputRef property on the <Input /> to get access the native hidden input field.

P.S. With the native version of the Select component, you can use the event to access the native select input field.

I do as React documentation does because if you use something like this

const setData = (name) => (e) => console.log(e.target)
export const MySelect = () => (
    <FormControl fullWidth>
        <InputLabel htmlFor="age-simple">Civilit茅</InputLabel>
        <Select
            value={'Monsieur'}
            onChange={setData(civility)}
            input={<Input />}
            label={'Civilit茅'}
        >
            <MenuItem value={'Mr'}>Monsieur</MenuItem>
            <MenuItem value={'Mme'}>Madame</MenuItem>
        </Select>
    </FormControl>
)

You create a function each time you rerender this Component, so using a depth 1 shallow comparison will return you to rerender each time.
I know that this not really usefull as actually material-ui components are rerender each time as when you rerender a child even with same same props, the props object is never the same wich cause a rerender of each material-ui components when you rerender the parent component.
The problem is actually that onChange event took 22ms to rerender my form, so it look like less reactive to user input.

I've been using the Formik library recently https://github.com/jaredpalmer/formik and it throws this warning:

Warning: You forgot to pass an `id` or `name` attribute to your input

Since event.target has no name or id, Formik can't figure out what input in the form changed. It works fine using <Select native> though.

Unless I'm missing something, using native selects is the only way around this?

@mgrijalva You are right. Formik uses the following logic. I'm curious about the integration with other form libraries. Still, you have an alternative on userland, you can write an adapter between Material-UI onChange output and Formik requirements. It's simple.

I'm adding the waiting for user feedback label. We might be able to implement something better on our side, but without more feedback, it's hard to tell. So please, people raised your voice if you encounter a similar situation.

So far we have:

  • @robininfo-edsx want to access the field name in his onChange.event.target.name.
  • @mgrijalva that has Formik constraints to deal with.

I have been looking at what Ant Design is doing with their custom select. They don't even expose the event (using rc-select internally). So the situation is worth on their end. I couldn't find any other benchmark. I think that we can keep the issue closed until further use cases is discovered.

@oliviertassinari I am struggling through Formik issues as well. When you say:

you can write an adapter between Material-UI onChange output and Formik requirements. It's simple.

What would an adapter look like in this scenario?

I just started playing with formik using material-ui components today. so far, it seems like you may need to write some wrapper components to integrate formik Field with MU components similar to the React Native related documentation on Formik's readme.

e.g., using render property on formik Field, you can pass a component that uses MU TextField and bind TextField.value to props.field.value and TextField.onChange to (event, newValue) => props.form.setFieldValue('someFormFieldName', newValue).

not sure if the most elegant way, but seems to be working.

oh, i saw that you can manipulate input.name via TextField.name property, so you can actually do TextField.onChange as props.form.handleChange if you set TextField.name properly. If you need to get the initial value, you also need to have TextField.value binding.

Hi @burcakulug, have you a running example of a solution that I could use for my project ? I struggle with this warning :-(

Hi @jfperrin , I have started this personal fun project, you can take a look at this as a sample.
https://github.com/burcakulug/mytvguide/blob/master/app/containers/UsersPage/index.js#L36

For my actual work project, I implemented some wrapper components using Formik's Field and Material UI components, if that project gets opensource, I can also post it here later. For now, the one above should give you the gist.

Hi, i'm also struggling with this warning when using Formik with the Material-ui SelectField. So i think it is enough for the devs to implement something better to resolve this issue.

i think a library wrapping mui components and bridging formik field to them would be good. that's what i sort of did for work, if i can manage, i may try to create a library for it.

I'm having the same issue.

I wrapped this for our team - it only took a few minutes.
If no one else creates a lib for it, I wouldn't mind sharing.

Here's an example for the TextField component:

import React from "react";
import PropTypes from "prop-types";
import { TextField } from "material-ui";

const MaterialInput = ({
  field,
  form: { touched, errors },
  label,
  ...props
}) => (
  <TextField
    {...props}
    {...field}
    value={field.value || ""}
    error={Boolean(touched[field.name] && errors[field.name])}
    label={(touched[field.name] && errors[field.name]) || label}
  />
);

MaterialInput.propTypes = {
  field: PropTypes.shape({
    name: PropTypes.string,
    value: PropTypes.any,
    onChange: PropTypes.func,
    onBlur: PropTypes.func
  }).isRequired,
  form: PropTypes.shape({
    touched: PropTypes.object,
    errors: PropTypes.object
    // the rest of the formik bag too
  }).isRequired,
  label: PropTypes.string.isRequired
};

export default MaterialInput;

Here's an example with Selects:

import React from "react";
import PropTypes from "prop-types";
import { TextField } from "material-ui";

const MaterialInputSelect = ({
  field,
  form: { touched, errors },
  label,
  options,
  ...props
}) => (
  <TextField
    {...props}
    {...field}
    value={field.value || ""}
    error={Boolean(touched[field.name] && errors[field.name])}
    label={(touched[field.name] && errors[field.name]) || label}
    select
    SelectProps={{ native: true }}
  >
    <option value="">Select</option>
    {options.map(option => (
      <option key={option.value} value={option.value}>
        {option.label}
      </option>
    ))}
  </TextField>
);

MaterialInputSelect.propTypes = {
  field: PropTypes.shape({
    name: PropTypes.string,
    value: PropTypes.any,
    onChange: PropTypes.func,
    onBlur: PropTypes.func
  }).isRequired,
  form: PropTypes.shape({
    touched: PropTypes.object,
    errors: PropTypes.object
    // the rest of the formik bag too
  }).isRequired,
  label: PropTypes.string.isRequired,
  options: PropTypes.arrayOf(
    PropTypes.shape({
      value: PropTypes.any,
      label: PropTypes.string
    })
  )
};

MaterialInputSelect.defaultProps = {
  options: []
};

export default MaterialInputSelect;

And here's an example with Switches:

import React from "react";
import PropTypes from "prop-types";
import { FormControlLabel, Switch } from "material-ui";

const MaterialInput = ({
  field: { value, ...field },
  form,
  label,
  ...props
}) => (
  <FormControlLabel
    control={<Switch {...field} {...props} checked={value || false} />}
    label={label}
  />
);

MaterialInput.propTypes = {
  field: PropTypes.shape({
    name: PropTypes.string,
    value: PropTypes.any,
    onChange: PropTypes.func,
    onBlur: PropTypes.func
  }).isRequired,
  form: PropTypes.shape({
    // the formik bag
  }).isRequired,
  label: PropTypes.string.isRequired
};

export default MaterialInput;

Chiming in late for anyone also struggling with this, since I'm using MUI with Formik as well, but wanted the non-native Select. It seems you can achieve it by passing an ID down to the visible element that matches the form field name, like so:

<TextField
  name="mySelectField"
  label="Select something ..."
  select
  required
  value={values.mySelectField}
  SelectProps={{
    SelectDisplayProps: {
      id: 'mySelectField'
    }
  }}
  onChange={handleChange}
  onBlur={handleBlur}
  >
  {mySelectOptions.map(option => (
    <MenuItem key={option.value} value={option.value}>
      {option.label}
    </MenuItem>
  ))}
</TextField>

FYI, this last snippet seems like MUI v1.x
0.x versions would similar to the previous snippets.

Was this page helpful?
0 / 5 - 0 ratings