Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to include placeholder in select component in Material UI@Next #11069

Closed
1 task done
NayanSrivastav opened this issue Apr 19, 2018 · 29 comments · May be fixed by nbv1982/material-ui#10 or hzhz/material-ui#96
Closed
1 task done
Labels
component: select This is the name of the generic UI component, not the React module! out of scope The problem looks valid but we won't fix it (maybe we will revisit it in the future)

Comments

@NayanSrivastav
Copy link

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

Expected Behavior

Select field should show Placeholder text.

Current Behavior

Placeholder text should be visible when passed to Input tag.

Your Environment

Tech Version
Material-UI v1.0.0-beta.42
React 16.1.0
browser Chrome( 65.0.3325.181)
@oliviertassinari oliviertassinari added the out of scope The problem looks valid but we won't fix it (maybe we will revisit it in the future) label Apr 20, 2018
@oliviertassinari
Copy link
Member

@NayanSrivastav This concern has already been raised in #8875. As far as the discussion went, the conclusion was that the placeholder property for a select component doesn't make sense. With the given information, it's not something we plan on addressing. Instead, @sakulstra has raised some great alternatives: #8875 (comment)

import React from "react";
import { render } from "react-dom";
import { Select } from "material-ui";
import { withStyles } from "material-ui/styles";
import { MenuItem } from 'material-ui/Menu';
import classNames from "classnames";

class App extends React.Component {
  state = {
      select: 'none'
  }

  handleChange = field => e => {
    this.setState({[field]: e.target.value})
  }

  render() {
    return (
      <div>
        <Select native defaultValue='none'>
          <option value="none" disabled>
            uncontrolled Native placeholder
          </option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
        <br />
        <Select native value={this.state.select} onChange={this.handleChange('select')}>
          <option value="none" disabled>
            controlled Native placeholder
          </option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
        <br />
        <Select native value={this.state.select} onChange={this.handleChange('select')}>
          {this.state.select === 'none' && <option value="none" disabled>
            Autohide after selection
          </option>}
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
        <br />
        <Select value={this.state.select} onChange={this.handleChange('select')}>
          <MenuItem value="none" disabled>
            controlled Non native placeholder
          </MenuItem>
          <MenuItem value="1">Option 1</MenuItem>
          <MenuItem value="2">Option 2</MenuItem>
          <MenuItem value="3">Option 3</MenuItem>
        </Select>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

https://codesandbox.io/s/wqq7059oq8

@oliviertassinari
Copy link
Member

oliviertassinari commented Apr 20, 2018

@NayanSrivastav Now, maybe it's a matter on improving the documentation or adding some warnings when people are trying to provide a placeholder.

@NayanSrivastav
Copy link
Author

@oliviertassinari I think alternatives suggested by @sakulstra are great and yeah document must be improved so that folks coming from stable mui get clear indication that placeholder are not supported directly.

Thanks for prompt reply and closing it now.

@oliviertassinari oliviertassinari added the component: select This is the name of the generic UI component, not the React module! label Apr 20, 2018
@hadasmaimon
Copy link

hadasmaimon commented Apr 29, 2020

@oliviertassinari this is a great alternative,
But can the placeholder option be grayed out, instead of being black?

select component with placeholder option:
image

textField placeholder (inside an autocomplete):
image

@alexanderkho
Copy link

@hadasmaimon I'm trying to do this as well, did you find a solution?

@hadasmaimon
Copy link

hadasmaimon commented May 5, 2020

@hadasmaimon I'm trying to do this as well, did you find a solution?

@alexanderkho
I sent a class name that set the color to gray, in case the value is equal to the default value

const defaultValue = translate('Select');

const isDefaultValue = value === defaultValue;
const selectClasses = classNames(className, { 'class-font-colour-gray': isDefaultValue });

<Select
   className={selectClasses}
   ...
/>

@cazroam
Copy link

cazroam commented Aug 6, 2020

You can override the theme styling with the following code:
MuiSelect: { root: { '&.MuiFilledInput-input': { color: 'grey', }, }, },

this will, however, make any selected item grey also, but that is my use-case so I'm ok with that 👍

@Johnrobmiller
Copy link

Johnrobmiller commented Apr 11, 2021

@hadasmaimon I'm trying to do this as well, did you find a solution?

@alexanderkho
I sent a class name that set the color to gray, in case the value is equal to the default value

const defaultValue = translate('Select');

const isDefaultValue = value === defaultValue;
const selectClasses = classNames(className, { 'class-font-colour-gray': isDefaultValue });

<Select
   className={selectClasses}
   ...
/>

Great ideas; I got your solution up and running on my own project. I like the idea of using React's useState() and useEffect() to dynamically change "useStyles()" properties (which I am assuming is what you did, although it is not in your code).

@slamjs
Copy link

slamjs commented Jul 21, 2021

you can use the following

displayEmpty={true}
renderValue={value => value?.length ? Array.isArray(value) ? value.join(', ') : value : 'placeholder'}

@artidataio
Copy link

artidataio commented Oct 5, 2021

I came here from TextField with the select props set to true. I was wondering why the placeholder doesn't show up. Apparently it's not supported. Maybe the docs in the TextField section needs to be updated too.

@Skagoo
Copy link

Skagoo commented Oct 28, 2021

you can use the following

displayEmpty={true}
renderValue={value => value?.length ? Array.isArray(value) ? value.join(', ') : value : 'placeholder'}

This is actually a very minimal and nice workaround, nice one!

@ahuseyn
Copy link

ahuseyn commented Nov 15, 2021

I prefer more native looking placeholder with InputLabel :

      const [itemType, setItemType] = useState("");
      //...

      <FormControl fullWidth>
        {itemType === "" ? (
          <InputLabel disableAnimation shrink={false} focused={false} id='item_type_label'>
            Item Type
          </InputLabel>
        ) : null}

        <Select id='item_type' labelId='item_type_label' value={itemType} onChange={itemTypeChange}>
          <MenuItem value={false}>Private</MenuItem>
          <MenuItem value={true}>Public</MenuItem>
        </Select>
      </FormControl>

@akramnarejo
Copy link

akramnarejo commented Dec 27, 2021

just provide following attributes into select
displayEmpty
renderValue={value !== "" ? undefined : () => "placeholder text"}

@MakakWasTaken
Copy link

MakakWasTaken commented Jan 18, 2022

just provide following attributes into select displayEmpty renderValue={value !== "" ? undefined : () => "placeholder text"}

This should instead be the following:

displayEmpty
renderValue={(value) => (value !== '' ? value : 'Placeholder text')}

Because the example you provided will cause the value not to show when it is selected. Also making the placeholder functional did not work for me.

It should also be mentioned that this only renders the value of the component. Not the label.

@sgentile
Copy link

you can use the following

displayEmpty={true}
renderValue={value => value?.length ? Array.isArray(value) ? value.join(', ') : value : 'placeholder'}

but if your label is different than your value - it will show the value instead of the label

@mixa9269
Copy link

Thank you all for your solutions!
I created a custom Select with a placeholder based on MUI select.

const Select = ({
  classes: {
    placeholder: placeholderClass,
    ...otherClasses
  },
  children,
  placeholder,
  multiple,
  renderValue: renderValueProp,
  ...otherProps
}) => {
  const renderValue = useCallback((value) => {
    if (value === '' || value?.length === 0) {
      return <span className={placeholderClass}>{placeholder}</span>;
    }

    const childrenArray = React.Children.toArray(children);

    if (multiple && Array.isArray(value)) {
      return childrenArray
        .filter(child => value.some(v => v === child.props.value))
        .map(child => child.props.children)
        .join(', ');
    }

    return childrenArray.find(child => child.props.value === value)?.props.children;
  }, [children, multiple, placeholder, placeholderClass]);

  return (
    <MUISelect
      classes={otherClasses}
      renderValue={renderValueProp || renderValue}
      multiple={multiple}
      {...otherProps}
    >
      {children}
    </MUISelect>
  );
};

Select.defaultProps = {
  displayEmpty: true,
};
placeholder: {
    opacity: 0.42, // if you use default theme
},

@teetotum
Copy link

teetotum commented Sep 6, 2022

I don't want to overwrite renderValue because I would lose the original behavior covering single and multiselect and would need to mimic the original behavior.
I think I found a workable css solution:

<Select
    sx={{
        '& .MuiSelect-select .notranslate::after': placeholder
            ? {
                content: `"${placeholder}"`,
                opacity: 0.42,
              }
            : {},
    }}
>
    {options}
</Select>

@HyoilKim
Copy link

HyoilKim commented Oct 14, 2022

displayEmpty
renderValue={(value) => (value !== '' ? value : 'Placeholder text')}

It doesn't work for me. An error appears. So I use renderValue={value === '' ? () => placeholder : undefined}

  Overload 1 of 2, '(props: Pick<SelectProps, "className" | "style" | "innerRef" | "ref" | "input" | "label" | "slot" | "title" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | ... 282 more ... | "SelectDisplayProps"> & StyledComponentProps<...>, context?: any): ReactElement<...> | ... 1 more ... | null', gave the following error.
    Type '(value: unknown) => unknown' is not assignable to type '(value: unknown) => ReactNode'.
      Type 'unknown' is not assignable to type 'ReactNode'.
        Type 'unknown' is not assignable to type 'ReactPortal'.
  Overload 2 of 2, '(props: PropsWithChildren<Pick<SelectProps, "className" | "style" | "innerRef" | "ref" | "input" | "label" | "slot" | "title" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | ... 282 more ... | "SelectDisplayProps"> & StyledComponentProps<...>>, context?: any): ReactElement<...> | ... 1 more ... | null', gave the following error.
    Type '(value: unknown) => unknown' is not assignable to type '(value: unknown) => ReactNode'.```

@MakakWasTaken
Copy link

displayEmpty
renderValue={(value) => (value !== '' ? value : 'Placeholder text')}

It doesn't work for me. An error appears. So I use renderValue={value === '' ? () => placeholder : undefined}

  Overload 1 of 2, '(props: Pick<SelectProps, "className" | "style" | "innerRef" | "ref" | "input" | "label" | "slot" | "title" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | ... 282 more ... | "SelectDisplayProps"> & StyledComponentProps<...>, context?: any): ReactElement<...> | ... 1 more ... | null', gave the following error.
    Type '(value: unknown) => unknown' is not assignable to type '(value: unknown) => ReactNode'.
      Type 'unknown' is not assignable to type 'ReactNode'.
        Type 'unknown' is not assignable to type 'ReactPortal'.
  Overload 2 of 2, '(props: PropsWithChildren<Pick<SelectProps, "className" | "style" | "innerRef" | "ref" | "input" | "label" | "slot" | "title" | "defaultChecked" | "defaultValue" | "suppressContentEditableWarning" | ... 282 more ... | "SelectDisplayProps"> & StyledComponentProps<...>>, context?: any): ReactElement<...> | ... 1 more ... | null', gave the following error.
    Type '(value: unknown) => unknown' is not assignable to type '(value: unknown) => ReactNode'.```

This seems like a typescript error, try this instead:

displayEmpty
renderValue={(value: string): React.ReactNode =>
  (value !== '' ? value : 'Placeholder text') as string
}

@HyoilKim
Copy link

Thank you! But, I have still an unresolved thing.

Because the example you provided will cause the value not to show when it is selected.

It doesn't be reproduced. For me, the value is shown when it's selected

@KrisKnez
Copy link

<Select
  defaultValue="default"
  sx={{
    '& input[value="default"] ~ .MuiSelect-select': {
      color: "#CCC",
    },
  }}
>
  <MenuItem value="default" sx={{ display: "none" }}>
    Filter users
  </MenuItem>
  <MenuItem value="allUsers">All users</MenuItem>
  <MenuItem value="blockedUsers">Blocked users</MenuItem>
</Select>;

This, in my opinion, is an elegant solution to this, unfortunately, it does not work because for some reason the input is not the first element in the Select component.
When using the input to store the state like this, the input should be the first element inside the block.
In CSS the ~ selector only works for sibling elements that come after the target element, in our situation the .MuiSelect-select element is before the input element.

image

@Pareder
Copy link

Pareder commented Jan 11, 2023

I have some related issue. If I am using disabled MenuItem as a placeholder, opening Select will highlight the next item after disabled one as focused. Reproduction link: https://codesandbox.io/s/dazzling-black-z1ner6?file=/src/App.js. Is it possible not to focus the next item?

Even in your example https://mui.com/material-ui/react-select/#placeholder the first option after placeholder is focused on select opening.

@croraf
Copy link
Contributor

croraf commented Jul 22, 2023

I think there is a clear need for this and not having this prop implemented already is very strange and arbitrary decision.

  1. Just from the number of comments and likes on this thread.

  2. The explainations why this is not needed in the comments in [Select] placeholder prop #8875 don't make sense (or make sense just in some cases).

    A. For example in the Select placeholder we want to tell the user how to interact with the field "Select one" or "Choose one" or "Pick one from the list" (to give better distinction from the other fields which might have a similar placeholder like "Pick a date", "Pick a time", "Enter text", "Choose one or many", "Type or select from the list", ....).

    B. Some might want to show the mentioned placeholder "Choose one" only in case the selection is required, so it adds another visual clue that the field is required along with the star.

  3. Consistency. In all other fields in our application (text fields, date pickers, time pickers, autocompletes, ...) except this one type we have a placeholder. (In our case the placeholder tells the user how to interact with the filed as mentioned in 2A ).

@dev0T
Copy link

dev0T commented Sep 8, 2023

I think there is a clear need for this and not having this prop implemented already is very strange and arbitrary decision.

Completely agree. For such a robust and heavy library, this is an use case to be expected and implemented. The example provided in the documentation shows this is a flaw and is anything but elegant.

The solutions provided here might work but it doesn't feel correct using such workarounds in corporate apps considering readability.

@damjanvucina
Copy link

damjanvucina commented Sep 19, 2023

Here's my neat little trick, just render this alongside the Select component:

          <FormLabel
            style={{
              marginLeft: '0.71em',
              marginTop: '-0.71em',
              paddingLeft: '0.44em',
              paddingRight: '0.44em',
              zIndex: 2,
              backgroundColor: 'white',
              position: 'absolute',
              fontSize: '0.75em',
            }}
          >
            {placeholder}
          </FormLabel>

This will make the select component's border box to have a placeholder at all times, so wrap the above code in a condition to only be rendered if the select is empty.

@croraf
Copy link
Contributor

croraf commented Sep 22, 2023

@damjanvucina Although not ideal, I think your soluton might serve as a temporary hack.

I just modified it a bit to fit better (one thing is that FormLabel renders label which does mess the layout):

{!value && !label && placeholder && (
        <div
          style={{
            top: "0.25em",
            zIndex: 2,
            backgroundColor: "transparent",
            position: "absolute",
            fontSize: "1em",
            color: "#BBB",
            fontWeight: "400",
            pointerEvents: "none",
          }}
        >
          {placeholder}
        </div>
)}

@UntamedLotus
Copy link

UntamedLotus commented Oct 17, 2023

@sgentile

you can use the following

displayEmpty={true}
renderValue={value => value?.length ? Array.isArray(value) ? value.join(', ') : value : 'placeholder'}

but if your label is different than your value - it will show the value instead of the label

Thanks😊. Works for me. I styled it after wrapping the string "placeholder" into span tag.

@Rafdadoumont
Copy link

Rafdadoumont commented Feb 22, 2024

Here is an easy workaround

displayEmpty 
renderValue={value !== "" ? undefined : () => <div style={{color:"#B1B1B1"}}>Placeholder</div>

@AnatolyGavrilov
Copy link

AnatolyGavrilov commented Jun 3, 2024

@NayanSrivastav This concern has already been raised in #8875. As far as the discussion went, the conclusion was that the placeholder property for a select component doesn't make sense. With the given information, it's not something we plan on addressing. Instead, @sakulstra has raised some great alternatives: #8875 (comment)

import React from "react";
import { render } from "react-dom";
import { Select } from "material-ui";
import { withStyles } from "material-ui/styles";
import { MenuItem } from 'material-ui/Menu';
import classNames from "classnames";

class App extends React.Component {
  state = {
      select: 'none'
  }

  handleChange = field => e => {
    this.setState({[field]: e.target.value})
  }

  render() {
    return (
      <div>
        <Select native defaultValue='none'>
          <option value="none" disabled>
            uncontrolled Native placeholder
          </option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
        <br />
        <Select native value={this.state.select} onChange={this.handleChange('select')}>
          <option value="none" disabled>
            controlled Native placeholder
          </option>
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
        <br />
        <Select native value={this.state.select} onChange={this.handleChange('select')}>
          {this.state.select === 'none' && <option value="none" disabled>
            Autohide after selection
          </option>}
          <option value="1">Option 1</option>
          <option value="2">Option 2</option>
          <option value="3">Option 3</option>
        </Select>
        <br />
        <Select value={this.state.select} onChange={this.handleChange('select')}>
          <MenuItem value="none" disabled>
            controlled Non native placeholder
          </MenuItem>
          <MenuItem value="1">Option 1</MenuItem>
          <MenuItem value="2">Option 2</MenuItem>
          <MenuItem value="3">Option 3</MenuItem>
        </Select>
      </div>
    );
  }
}

render(<App />, document.getElementById("root"));

https://codesandbox.io/s/wqq7059oq8

Can you explain please why you said MUI select placeholder doesn't make sens?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: select This is the name of the generic UI component, not the React module! out of scope The problem looks valid but we won't fix it (maybe we will revisit it in the future)
Projects
None yet