JedWatson / react-select

The Select Component for React.js
https://react-select.com/
MIT License
27.44k stars 4.1k forks source link

Disable Chrome autofill allowing autoComplete="new-password" to be passed into the Input component properties #3500

Open rdsedmundo opened 5 years ago

rdsedmundo commented 5 years ago

Currently, the default Input component is forcefully setting autoComplete="off" which doesn't disable Chrome's autofill functionality on latest browser versions. For achieving that, we need to pass autoComplete="new-password".

The problem is that the value is hardcoded and can't be configured unless we implement our own Input component and configure this property properly there.

It's hardcoded here: https://github.com/JedWatson/react-select/blob/ba76246a92fe9371b5d4f8795d30119b045dcaba/src/Select.js#L1401

And it gets passed to the default Input component here: https://github.com/JedWatson/react-select/blob/ba76246a92fe9371b5d4f8795d30119b045dcaba/src/components/Input.js#L53

As you can see, there's no way of configuring that property manually.

kylehurt-rkv commented 5 years ago

I agree that this would be a welcome change. I have been fighting with this for the last couple of days. It is a dealbreaker for our app because the autocomplete control overlays the react-select menu. We have resorted to forking the project, but it would be nice if this were supported natively.

Rall3n commented 5 years ago

As you can see, there's no way of configuring that property manually.

@rdsedmundo Yes, there is a possibility to configure it. It is hard coded as a prop to the Input component, that is true. But this library offers in its current version the possibility to overwrite its internal components using its components framework.

You just have to overwrite the Input component and overwrite the prop being passed to the component.

import Select, { components } from 'react-select';

const Input = ({ autoComplete, ...props }) => <components.Input {...props} autoComplete="new-password" />;

<Select
    { ... }
    components={{
        Input
    }}
/>

We have resorted to forking the project, but it would be nice if this were supported natively.

@kylehurt-rkv Here is your native support, without forking the repository.

rdsedmundo commented 5 years ago

That's a smarter solution than what I did. I knew I could just overwrite the Input, that's what I did for fixing it, but I just copied its source code originally and pasted on my codebase. I haven't thought about the possibility of just importing it from the package.

I still can see value of having it configurable though.

kylehurt-rkv commented 5 years ago

@Rall3n We actually tried that exact solution, but whenever the page would try to load, Chrome would get of memory errors and crash. We are supplying several other custom components into the Select component, so it may be possible that something we did on one of the other custom components was conflicting with custom input component. Or maybe we are doing something in a non-standard way. Here is our code with the addition of the custom input component. Chrome throws an out of memory error and never loads the page. I'm guessing there is a circular reference somewhere. ` import React from 'react'; import PropTypes from 'prop-types'; import RSelect from 'react-select'; import { withStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import NoSsr from '@material-ui/core/NoSsr'; import TextField from '@material-ui/core/TextField'; import Paper from '@material-ui/core/Paper'; import MenuItem from '@material-ui/core/MenuItem'; import FormControl from '@material-ui/core/FormControl/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText/FormHelperText'; import { remove } from 'react-icons-kit/fa/remove'; import { chevronDown } from 'react-icons-kit/fa/chevronDown'; import { Icon } from 'react-icons-kit'; import Tooltip from '@material-ui/core/Tooltip'; import { isErrorState } from './reduxFormHelper';

const styles = theme => ({ root: { flexGrow: 1, }, input: { display: 'flex', paddingTop: 2, paddingBottom: 3, }, valueContainer: { display: 'flex', flexWrap: 'wrap', flex: 1, alignItems: 'center', }, noOptionsMessage: { padding: ${theme.spacing.unit}px ${theme.spacing.unit * 2}px, }, singleValue: { fontSize: 16, }, placeholder: { position: 'absolute', left: 2, fontSize: 16, }, paper: { position: 'absolute', zIndex: 1, marginTop: 0, paddingTop: 0, left: 0, right: 0, }, });

class Select extends React.Component { findOption() { const { input, options } = this.props; const value = input.value._id || input.value; return options.find(option => option.value === value); }

render() { const { classes, theme, fullWidth, label, id, placeholder, required, meta: { touched, error }, options, isReadOnly, input, } = this.props;

function NoOptionsMessage(props) {
  return (
    <Typography
      color="textSecondary"
      className={props.selectProps.classes.noOptionsMessage}
      {...props.innerProps}
    >
      {props.children}
    </Typography>
  );
}

function inputComponent({ inputRef, ...props }) {
  return <div ref={inputRef} {...props} />;
}

const Control = props => {
  return (
    <div className="app-input-group">
      <FormControl
        fullWidth={fullWidth}
        error={isErrorState(touched, error)}
      >
        <TextField
          error={isErrorState(touched, error)}
          label={label}
          id={id}
          required={required}
          InputProps={{
            inputComponent,
            inputProps: {
              className: props.selectProps.classes.input,
              inputRef: props.innerRef,
              children: props.children,
              ...props.innerProps,
            },
          }}
          {...props.selectProps.textFieldProps}
        />
        <FormHelperText>{touched && error}</FormHelperText>
      </FormControl>
    </div>
  );
};

function Option(props) {
  return (
    <MenuItem
      buttonRef={props.innerRef}
      selected={props.isFocused}
      component="div"
      style={{
        fontWeight: props.isSelected ? 500 : 400,
      }}
      {...props.innerProps}
    >
      {props.children}
    </MenuItem>
  );
}

function Placeholder(props) {
  return (
    <Typography
      color="textSecondary"
      className={props.selectProps.classes.placeholder}
      {...props.innerProps}
    >
      {props.children}
    </Typography>
  );
}

function SingleValue(props) {
  return (
    <Typography
      className={props.selectProps.classes.singleValue}
      {...props.innerProps}
    >
      {props.children}
    </Typography>
  );
}

function ValueContainer(props) {
  return (
    <div className={props.selectProps.classes.valueContainer}>
      {props.children}
    </div>
  );
}

function Menu(props) {
  return isReadOnly ? null : (
    <Paper
      square
      className={props.selectProps.classes.paper}
      {...props.innerProps}
    >
      {props.children}
    </Paper>
  );
}

function ClearIndicator(props) {
  const {
    innerProps: { ref, ...restInnerProps },
  } = props;
  return (
    <div
      {...restInnerProps}
      ref={ref}
      className="app-select-clear-ind-container"
    >
      <Tooltip title="Clear">
        <Icon icon={remove} className="app-select-clear-ind" />
      </Tooltip>
    </div>
  );
}

function DropdownIndicator(props) {
  return (
    <div className="app-select-dd-ind-container">
      <Icon className="app-select-dd-ind" icon={chevronDown} />
    </div>
  );
}

function IndicatorsContainer(props) {
  return isReadOnly ? null : (
    <div className="app-select-ind-container">{props.children}</div>
  );
}

function IndicatorSeparator(props) {
  return <span className="app-select-ind-sep" {...props.innerProps} />;
}

const Input = ({ autoComplete, ...props }) => (
  <components.Input {...props} autoComplete="new-password" />
);

const components = {
  Control,
  Menu,
  NoOptionsMessage,
  Option,
  Placeholder,
  SingleValue,
  ValueContainer,
  DropdownIndicator,
  ClearIndicator,
  IndicatorsContainer,
  IndicatorSeparator,
  Input,
};

const selectStyles = {
  input: base => ({
    ...base,
    color: theme.palette.text.primary,
    '& input': {
      font: 'inherit',
    },
  }),
};

return (
  <div className={classes.root}>
    <NoSsr>
      <RSelect
        isClearable
        classes={classes}
        styles={selectStyles}
        isDisabled={isReadOnly}
        textFieldProps={{
          InputLabelProps: {
            shrink: true,
          },
        }}
        options={options}
        components={components}
        value={this.findOption()}
        onChange={
          !isReadOnly
            ? option => input.onChange(option ? option.value : null)
            : null
        }
        placeholder={placeholder}
        openMenuOnClick={!isReadOnly}
      />
    </NoSsr>
  </div>
);

} }

Select.defaultProps = { fullWidth: true, };

Select.propTypes = { classes: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, };

export default withStyles(styles, { withTheme: true })(Select);

`

kylehurt-rkv commented 5 years ago

image This the debugger window Chrome throws up.

Rall3n commented 5 years ago

@kylehurt-rkv If I´m seeing this correctly, you are creating the custom components inside your render function? This could be the source of your problem.

Creating the components inside the render causes a complete rerender, because with each call of render the variables are recreated. Try declaring the components outside the render function (I would recommend outside of the class if you can, else inside the class).

kylehurt-rkv commented 5 years ago

@Rall3n Turns out I was right. I WAS doing something in a non-standard way. Moving the custom components outside of the class took care of the issue I was having with Chrome crashing. Makes sense now that I know the answer. Thank you very much for your insight.

lvl99 commented 5 years ago

Seems like there's a good reason to have this autoComplete configurable, as based on the age and responses of https://github.com/JedWatson/react-select/pull/2395

gregholst commented 4 years ago

I have a similar problem in #4006 : I am looking for a way to make sure that the Dashlane autofill icon does not get active in my AsyncComponent which results in a crash of the Chrome browser. Dashlane support hasn't been helpful so far, so if anybody of you has an idea on how to lock Dashlane out of my AsyncComponent, i'd be happy to hear!

ebonow commented 3 years ago

This seems somewhat unnecessary given the existing component api as Rall3n has already mentioned.

Here you can already pass in an autoComplete prop to the Select and have it rendered in a custom Input component.

Note: This can be applied to any prop you want to apply to the Input

Working demo: codesandbox

const Input = (props) => {
  const { autoComplete = props.autoComplete } = props.selectProps;
  return <components.Input {...props} autoComplete={autoComplete} />;
};

const MySelect = (props) => (
   <Select components={{ Input }} autoComplete="new-password" options={options} />
);
barrychapman commented 3 years ago

This really needs to be added

ebonow commented 3 years ago

Greetings all,

It seems that Google has perhaps changed their stance on this and per the top answer here, autocomplete=off appears to now be working as expected. Can anyone confirm if this is the case or find any documentation supporting this?

In case this isn't correct, I have filed the following comment in the relevant the Chromium bugs thread: https://bugs.chromium.org/p/chromium/issues/detail?id=587466#c591

ebonow commented 3 years ago

Greetings all,

It seems per the above stackoverflow post and in my own testing that newer versions of Chrome do appear to respect "autocomplete=off". If anyone is experiencing any differently, please provide a codesandbox example so we can reproduce and investigate further.

If anyone needs or wants to change this attribute on the Input, this is already possible as provided here: https://github.com/JedWatson/react-select/issues/3500#issuecomment-748568931

As such I will be closing this issue, but happy to re-open if we can confirm that this is still an issue.

ebonow commented 3 years ago

Looked into this further and several users are still experiencing autoComplete from Chrome. I will reopen this.

philipaarseth commented 2 years ago

This is still a problem in safari and setting custom input component and autoComplete="new-password" doesn't always fix it.

It may have something to do with situational rendering

Wrapping with this works <div className={showingMenu ? "": "hidden"}> and hidden is display: none;

Wrapping with this does not work {showingMenu &&

edit: name: search seems to work better https://stackoverflow.com/a/30873633

OperKH commented 1 year ago
      <Input
        {...commonProps}
        autoCapitalize="none"
        autoComplete="off"
        autoCorrect="off"
        id={id}
        innerRef={this.getInputRef}
        isDisabled={isDisabled}
        isHidden={inputIsHidden}
        onBlur={this.onInputBlur}
        onChange={this.handleInputChange}
        onFocus={this.onInputFocus}
        spellCheck="false"
        tabIndex={tabIndex}
        form={form}
        type="text"
        value={inputValue}
        {...ariaAttributes}
      />

I see that search input is hardcoded to type="text". If I change it to type="search" - it solves issue with browser autofill.