facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
229.93k stars 47.13k forks source link

Provide a way to handle browser-autocompleted form values on controlled components #1159

Open ericflo opened 10 years ago

ericflo commented 10 years ago

When there's a controlled component for form names that the user has saved in their browser (common with username/password fields), the browser will sometimes render the page with values in those fields without firing onChange events. If the user submits the form, the component state does not reflect what is showing to the user.

In experimenting with this, it appears that the data is there on load (tested by logging this.refs.myinput.getDOMNode().value)

ericflo commented 10 years ago

This seems to discuss it a bit more: http://stackoverflow.com/a/11710295

0x6a74 commented 10 years ago

atm i am using this https://github.com/tbosch/autofill-event

visionscaper commented 9 years ago

cc me

sophiebits commented 9 years ago

(@visionscaper tip: press "Subscribe" on the right column.)

uberllama commented 9 years ago

Any updates or suggested best practices on this one? The autofill event polyfill seems like a sledgehammer solution.

sfnelson commented 9 years ago

Safari (8) does dispatch change events on autofill, but they don't bubble so they don't reach the react handler.

vipulnsward commented 9 years ago

A related discussion on https://github.com/angular/angular.js/issues/1460 was closed, with some suggestions, one of them being making use of https://github.com/tbosch/autofill-event to fire change event on autofill, manually.

I think, we can close here as well.

jbaudanza commented 9 years ago

It would be nice to have the autofill-event as part of React, possible as an addon. This functionality is going to be necessary for virtually everyone using React for form validation. The autofill-event script also adds a dependency for jQuery, which may be undesirable in many cases.

reconbot commented 9 years ago

This is a browser bug that effects all browsers a little differently. I think just because angular punted on trying to work around or fix it doesn't mean react should. It's a common thing but I understand if this is a "wontfix".

I'm not super well versed on the various browser's bug trackers, but it would be nice if someone who is could find or open issues for this. I think the react team has more weight then most users would when opening an issue about it. And we could tract the tickets here for the react developers who are interested.

reconbot commented 9 years ago

I caught up with the angular thread. Firefox has issues with password fields (https://bugzilla.mozilla.org/show_bug.cgi?id=950510 is still open), no word on safari's bug #, chrome is fixed.

vipulnsward commented 9 years ago

Issues are already being tracked at some places - https://github.com/angular/angular.js/issues/1460#issuecomment-53947546

ZebraFlesh commented 8 years ago

The Chrome issue is now closed, but it pretty clearly does not work in Chrome 50 with React 0.14.8.

devdlabs commented 8 years ago

no fix yet?

tirdadc commented 8 years ago

I feel like this was working for a while and broke again recently?

okmanideep commented 8 years ago

any update on this issue?

ghost commented 8 years ago

Here an extract of my solution for this issue:

export default class Input extends Component {
  static propTypes = {
    value: PropTypes.string,
    onFieldChange: PropTypes.func,
  };

  static defaultProps = {
    value: '',
  }

  componentDidMount() {
    this._listener = setInterval(() => {
      if (!this.input || this._previousValue === this.input.value) {
        return;
      }

      this._previousValue = this.input.value;

      const evt = document.createEvent('HTMLEvents');
      evt.initEvent('input', true, true);
      this.input.dispatchEvent(evt);
    }, 20);
  }

  componentWillUnmount() {
    clearInterval(this._listener);
  }

  refInput = (input) => this.input = input;

  render() {
    const { label,
      value,
      onFieldChange,
    } = this.props;

    this.input = this.input || { value };

    return (
        <input
            value={this.input.value}
            onChange={onFieldChange}
            ref={this.refInput}
        />
    );
  }
}

NOTE: value comes from the state and onFieldChange updates the state with the new value

The setInterval code is from the https://github.com/Pephers/react-autofill

krausest commented 7 years ago

Can anyone confirm that this issue was fixed with iOS 10.2? I can't reproduce it now...

irisSchaffer commented 7 years ago

I'm still having problems on iOS 10.2 with Chrome version 55.0.2883.79... Contrary to what is described above though, for me the autofilled content flashes up briefly in the form and is then removed again. So it's now consistent with what is stored in the state, however, autofill still doesn't work...

patriciarealini commented 7 years ago

https://github.com/facebook/react/issues/8174

juannadin commented 7 years ago

I'm reproducing the same issue encountered by @irisSchaffer. I have an isomorphic application, where I render a static page with react and express, then on the client I export the props and use the same page component.

Even when not handling the password input value through react, the client react component deletes the saved password from the input, even when chrome shows it briefly. When I disable javascript from the browser, the saved password stays in its place.

This issue is visible on Chrome only, using its last version, 58.

Not related to react, the saved password is not accessible through element.value until there's an event fired on the dom, like focus, any key event, etc. Tried simulate an event fire through javascript, with no luck on the fill of the value property.

vikas-parashar commented 7 years ago

+1. any fix or hacks?

robsonvn commented 7 years ago

I'm still facing this problem on iOS 10.3.3 Chrome 60.0.3112.89.

The auto-fill works, fills the field but doesn't change the state.

jcalfee commented 7 years ago

Decorator work-around: https://github.com/Pephers/react-autofill

Generic component work-around:

/**
  Trigger onChange event after browser auto-fill.

  @see https://github.com/facebook/react/issues/1159
  @example <AutoFillWatch component={ref =>
    <input required type="password" ref={ref} />
  }/>
*/
class AutoFillWatch extends Component {
  static propTypes = {
    // Auto-fill component like: <input type="password"
    component: PropTypes.func.isRequired,

    // component pass-through reference
    ref: PropTypes.func,
  }

  componentDidMount() {
    this._listener = setInterval(() => {
      if (!this.input || this._previousValue === this.input.value) {
        return;
      }

      this._previousValue = this.input.value;

      const evt = document.createEvent('HTMLEvents');
      evt.initEvent('input', true, true);
      this.input.dispatchEvent(evt);
    }, 100);
  }

  componentWillUnmount() {
    clearInterval(this._listener);
  }

  componentDidMount() {
    const {ref} = this.props
    if(ref) {
      console.log('ref', this.input);
      ref(this.input)
    }
  }

  refInput = (input) => this.input = input;

  render() {
    const {component} = this.props
    return component(this.refInput)
  }
}
oscar-b commented 7 years ago

Chrome for iOS (make sure you test with autofill from Chrome, and not the suggestions from the iOS keyboard) only emits change when autofilling values. Mobile Safari on the other hand emits focus, keydown, input, keyup, change and blur, as does Chrome and Safari on Mac.

React doesn't use change event for regular <input /> which seems to be the root cause of this issue: https://github.com/facebook/react/blob/e932ad68bed656eed5295b61ba74e5d0857902ed/src/renderers/dom/shared/eventPlugins/ChangeEventPlugin.js#L66-L71

Wouldn't it be possible to have React onChange also trigger on DOM change events? Would there be any harm in that?

Related issue for Chrome iOS: https://bugs.chromium.org/p/chromium/issues/detail?id=705275

I've tested the autofilling and events with the following little page:

<html>

<head>
  <script type="text/javascript">
    window.onload = function () {
      let elements = document.querySelectorAll('input');
      let actions = document.getElementById('actions')
      let events = 'focus blur keydown keyup change input'.split(' ');

      elements.forEach(element => {
        events.forEach(event => {
          element.addEventListener(event, e => {
            console.log(e);
            let eTypeNode = document.createTextNode(element.name + ' > ' + e.type + ":" + e.code + ":" + e.keyIdentifier);
            actions.appendChild(eTypeNode);
            actions.appendChild(document.createElement('br'));
          })
        })
      })
    };
  </script>
</head>

<body>
  <form>
    <input type="text" name="name" id="a" autocomplete="name" />
    <input type="email" name="email" id="b" autocomplete="email" />
    <input type="tel" name="tel" id="c" autocomplete="tel" />
  </form>
  <div id="actions"></div>
</body>

</html>
piotr-cz commented 7 years ago

Change event is emitted in Chrome v62 when you wrap inputs by a <form /> element.

imakatman commented 7 years ago

theres an onInput event handler that will grab the value of an input field if it changes.

tconroy commented 6 years ago

Still seeing some unusual behavior here with autofill on iOS. The above react-autofill decorator seems to work well with simple autofill but not the autofill shortcuts provided by the keyboard.

Daniel15 commented 6 years ago

This should be fixed in Chrome for iOS now: https://chromium.googlesource.com/chromium/src/+/55518e17850cac0bdc6fca7b24092bea479e34db. I'm not sure when this patch will be included in a released version though.

JackieLs commented 6 years ago

为什么一定要用 onChange? Why do we HAVE to use 'onChange' but not the 'change event'

class Input extends Component {
    componentDidMount(){
        this.input.addEventListener('change', (e)=>{
            this.props.onChange(this.props.field, e.target.value)
        })
    }
    render(){
        const { className, name, label, autofill, field, onBlur, onChange, value, error, visited, required, type, maxLength } = this.props
        return (
            <div className={ [styles.inputWrap, className || ''].join(' ') } onBlur={e => onBlur(field)}>
                <input ref={n => this.input = n} id={field} name={name || field} type={ type || 'text' } defaultValue={ value }
                 autoComplete={ autofill } maxLength={maxLength} />
                <div className={ [styles.placeholder, value? styles.active:''].join(' ') }>
                    <FormattedMessage id={label} />
                    <i className={styles.required}>{required? '*':''}</i>
                    <span className={ styles.error }>{ visited? error:'' }</span>
                </div>
            </div>
        )
    }
}
catamphetamine commented 6 years ago

Change event is emitted in Chrome v62 when you wrap inputs by a <form /> element.

I confirm that this solution works for Chrome for autofilling phone numbers via autoComplete="tel" https://github.com/catamphetamine/react-phone-number-input/issues/101

sekoyo commented 6 years ago

The solutions above using this._previousValue !== this.input.value unfortunately only work for iOS Safari. iOS Chrome's .value is still empty after an autofill.

I have no idea how people have been handling autofill in forms when using controlled components. Consider this login scenario:

Unless there's a way to get Chrome to report the value I can't think of a solution other than using uncontrolled components, html5 input validation, and getting form values on submit like the old days.

You can track (and star) the issue over here: https://bugs.chromium.org/p/chromium/issues/detail?id=813175

wlingke commented 6 years ago

I couldn't get the solution above to work either @DominicTobias. There's multiple layers of problems. The first was that this.input.value always came back as blank. However, I discovered, that if you use document.getElementById in each loop, then document.getElementById(id).value -- you'll get a value back.

This doesn't solve the dispatchEvent issue, but at least you can get the autofilled value and can do stuff with it (for example just calling a controlled input's onChange handler).

sekoyo commented 6 years ago

@wlingke good to know (and how strange)! I'll have to go down that route too if they don't fix the bug in time for my release

sekoyo commented 6 years ago

@wlingke When setting up a demo for the Chrome guys I realised something - this isn't an issue with Chrome I should have read the comments above more carefully (in particular @oscar-b comment). onChange is a synthetic event in React which doesn't actually use the change event, which seems pretty weird to me.

You can even see in the demo below that it behaves differently, more like it's listening for an input event. When you use the real change event then it works properly in iOS Chrome.

So I would suggest getting a ref to the element and just listening to the real change event, as well as the onChange event which seems to be the input event if you need to update state as the user is typing.

demo: https://kind-stallman-f3c045.netlify.com/

wlingke commented 6 years ago

@DominicTobias I previously tried both change and input events separately as well as both at once in that decorator but wasn't able to get it to work in my app.

Also I'm not sure if this changes things.. but there's a difference when autofilling ONE field versus autofilling MULTIPLE fields. The input event worked fine when autofilling one field. However, in mobile chrome you can actually autofill a set of fields at once from the keyboard (like firstname, lastname, email all with one tap).

In that second scenario, the decorator allows the first field autofills properly (first field meaning the field that is currently in focus). However, the decorator (which is attached to all 3 fields), doesn't seem to work on the other fields.

sekoyo commented 6 years ago

@wlingke Interesting, I added some more fields but when autofilling all three at once in iOS Chrome I am correctly receiving the change events natively (but 0 for the React version below):

screen shot 2018-03-08 at 19 29 15

(Screenshot is from desktop browser but I'm getting the same result on my phone)

wlingke commented 6 years ago

@DominicTobias I think if you use the input event - which is what I previously used, it'll work fully in Desktop. And work for the first input in Mobile.

input event is closest to react's onChange. The change event only files after an element loses focus from what I understand (https://stackoverflow.com/questions/17047497/what-is-the-difference-between-change-and-input-event-for-an-input-element)

sekoyo commented 6 years ago

Yep react has for some reason bound onChange to input event: https://stackoverflow.com/questions/38256332/in-react-whats-the-difference-between-onchange-and-oninput

So to keep things simple I just also call it in my Input component for actual change events (as well as the normal input ones). Now it's working in iOS Chrome. Try it out and let me know!

componentDidMount() {
  this.inputRef.addEventListener('change', this.onChange);
}

onChange = (event) => {
  // React actually uses the input even for onChange, this causes autofill to
  // break in iOS Chrome as it only fires a change event.
  if (this.props.onChange) {
    this.props.onChange(event);
  }
}

componentWillUnount() {
  this.inputRef.removeEventListener('change', this.onChange);
}

Edit: Chrome is supposed to have fixed in v65 but people are reporting it still doesn't work :/

Thank you for the explanation Dominic. We did fix that behavior back in December. We now fire keydown, change, input, and keyup events. https://chromium-review.googlesource.com/c/chromium/src/+/771674

We also had a fix for React. https://chromium-review.googlesource.com/c/chromium/src/+/844324

while this does fix the autofill behavior, it still does not invoke the onChange handler.

I also raised an issue on this and the dev was sure it was going to be fixed in 65... https://bugs.chromium.org/p/chromium/issues/detail?id=813175

antoinerousseau commented 6 years ago

I'm also listening to the native change event like @DominicTobias does and it works for me in iOS Chrome. I'm triggering redux-form's onChange in the native event listener attached on mount.

I'm also using the input:-webkit-autofill selector for styling.

See the result for yourself at the bottom of https://labrewlangerie.com (contact form).

oscar-b commented 6 years ago

Seems like this fix is finally on its way into Chrome iOS! Not sure in what version though, since this seems to have been merged on December 1.

https://bugs.chromium.org/p/chromium/issues/detail?id=705275#c11 https://chromium-review.googlesource.com/c/chromium/src/+/771674

Uter1007 commented 6 years ago

https://chromium.googlesource.com/chromium/src/+/55518e17850cac0bdc6fca7b24092bea479e34db

https://storage.googleapis.com/chromium-find-releases-static/555.html#55518e17850cac0bdc6fca7b24092bea479e34db

it should be already in the newest chrome. I am still facing this issue (Angular 5).

batjko commented 6 years ago

Same here in Chrome 65, React 15.6.

sekoyo commented 6 years ago

That's a shame, Mahmadi said it was going in on March 13th on v65 :(

Comment 15 by mahmadi@chromium.org, Mar 9 The fix isn't in Chrome 64. My bad. Chrome 65 will have (to be rolled out starting March 13th). I just tested your native events demo website and it works on Chrome 65. I appreciate you following up on this.

https://bugs.chromium.org/p/chromium/issues/detail?id=813175

Try going here on your phone and checking: https://kind-stallman-f3c045.netlify.com/native.html

sekoyo commented 6 years ago

What's really strange is that in v65 it's working natively, but even though it's getting an input even it's not being received in React! Need further investigation I'm not exactly sure how react is attaching the handler (or if it's doing it on the document element like a click handler for example).

Natively I can see the input event happening now (https://kind-stallman-f3c045.netlify.com/native.html):

native

But somehow React isn't receiving any of those events (https://kind-stallman-f3c045.netlify.com):

react

filipstoklasa commented 6 years ago

add onBlur={this.handleChange} on input

apnerve commented 6 years ago

KlarnaUI team has an interesting hack to deal with this issue exploiting the onAnimationStart event and :-webkit-autofill pseudo class as described in this article - https://medium.com/@brunn/detecting-autofilled-fields-in-javascript-aed598d25da7

We've used this hack in our applications it works fine (could test only on Chrome). I'll try to come up with a shim using this technique so that it is generic.

dusnoki commented 6 years ago

Any update on the issue?

Aarbel commented 6 years ago

up, what's the best trick for the moment ?

Aarbel commented 6 years ago

@ericflo @sophiebits

Pixelatex commented 6 years ago

This issue still seems to persist, any updates?