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

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

Open
ericflo opened this issue Feb 22, 2014 · 87 comments

Comments

@ericflo
Copy link
Contributor

ericflo commented Feb 22, 2014

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
Copy link
Contributor Author

ericflo commented Feb 22, 2014

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

@0x6a68
Copy link

0x6a68 commented Nov 17, 2014

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

@visionscaper
Copy link

cc me

@sophiebits
Copy link
Collaborator

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

@uberllama
Copy link

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

@sfnelson
Copy link

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

@vipulnsward
Copy link
Contributor

A related discussion on angular/angular.js#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
Copy link

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
Copy link

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
Copy link

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
Copy link
Contributor

Issues are already being tracked at some places - angular/angular.js#1460 (comment)

@ZebraFlesh
Copy link

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

@devdlabs
Copy link

no fix yet?

@tirdadc
Copy link

tirdadc commented Jun 17, 2016

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

@okmanideep
Copy link

any update on this issue?

@dwoodwardgb
Copy link

dwoodwardgb commented Oct 3, 2019

For those who came across this issue recently, it is caused intentionally by browsers to avoid security risks.

If we are able to access autofilled values once the page loads, it would become very easy to write malicious code to extract information on a form intended for phishing. Giving the user no chance to react. Probably part of the reason why autocomplete="off" is ignored by browsers.

To overcome this problem we need to:

  1. Accept the fact that we won't (and never will) have access to autofilled values when the DOM node mounts
  2. Embrace the security concerns which are brought up by the browsers

In order to solve this problem, think back to how forms were originally designed and intended to be used. When the user clicks a <button type="submit" /> inside a <form> it's basically saying "hey this action is intended by the user, now let's evaluate the form values".

Consider this example of a really simple SignInForm written in React:

import React from 'react';

class SignInForm extends React.Component {
  constructor() {
    this.state = {
      email: '',
      password: '',
    };

    this.handleEmail = this.handleEmail.bind(this);
    this.handlePassword = this.handlePassword.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleEmail(event) {
    this.setState({ email: event.target.value });
  }

  handlePassword(event) {
    this.setState({ password: event.target.value });
  }

  handleSubmit() {
   console.log(this.state.email); // The email value
   console.log(this.state.password); // The password value
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
         <input
            name="email"
            type="text"
            value={this.state.email}
            onChange={this.handleEmail}
            placeholder="Email"
          />
         <input
            name="password"
            type="text"
            value={this.state.password}
            onChange={this.handlePassword}
            placeholder="Password"
          />
          <button type="submit" onClick={this.handleSubmit}>Sign In</button>
        </form>
    );
  }
}

export default SignInForm;

Upon submission, the form will update and evaluate the inputs fields accordingly.

This means:

  1. We cannot disable submit button at all
  2. All the validation must occur once the user clicks this button

In my opinion, this is the most pragmatic approach and will save a lot of headache. I hope this helps others in the future.

Edit: For those who are unfamiliar with React, you can have a look at this link for a basic implementation of a form and some additional juicy info: https://html.spec.whatwg.org/multipage/forms.html

Thanks for the summary, however I am thoroughly confused. You say that when we submit a form, it will "update accordingly". Does this mean it will fire additional change events for each field it auto filled before firing the submit event? Or does it mean that when the submit event is fired the values are now readable via refs? If it's the former then we can build components like you showed in the snippet. If it's the latter then we will need to adopt a ref based approach to forms moving forward.

@thomasjulianstoelen Which did you mean?

@sploders101
Copy link

@dwoodwardgb
In my experience (with chrome, at least), it has been updating the fields on the user's first interaction with the page (clicks, form submissions, etc), and has been submitting events with the change, allowing the model variables to update. I haven't tested other browsers yet though.

deanishe added a commit to deanishe/shiori that referenced this issue Dec 13, 2019
Add <form> element, as some browsers won't autofill fields
not contained in form.

Remove username and password bindings, and explicitly read
inputs instead. Some browsers do not fire onChange events
when a field is autofilled for security reasons, so Vue
bindings don't register the change.

facebook/react#1159 (comment)
deanishe added a commit to deanishe/shiori that referenced this issue Dec 13, 2019
Add <form> element, as some browsers won't autofill fields
not contained in form.

Remove username and password bindings, and explicitly read
inputs instead. Some browsers do not fire onChange events
when a field is autofilled for security reasons, so Vue
bindings don't register the change.

facebook/react#1159 (comment)
@jkiss
Copy link

jkiss commented Jan 23, 2020

You could set autocomplete="off" attribute(HTML5), or record input value in url hash.

@nfiacco
Copy link

nfiacco commented Feb 11, 2020

Dug into this issue a bit more: Chromium made a change to emit both "change" and "input" native events based on this bug report: https://bugs.chromium.org/p/chromium/issues/detail?id=813175. However, for some reason React still does not trigger the onChange event in response to either of these native events. This is likely related to either React's deduplication logic (#10135) or the fact that events emitted programmatically by Chrome on iOS carry the isTrusted=false flag.

Can someone from the React team take a deeper look into the React onChange event handler to figure out why the event isn't being triggered? @gaearon or @zpao (random guesses) ?

In the meantime, I'm using the onInput event, which listens for the native "input" event directly.

@nfiacco
Copy link

nfiacco commented Feb 11, 2020

I think this is the commit in Chrome that broke the behavior:

https://chromium.googlesource.com/chromium/src/+/49bf3cbf7cc6d70643197a604090f8729f9d7404%5E%21/components/autofill/ios/fill/resources/fill.js

It removes the simulated flag from the change event, which probably causes the dedupe logic to ignore these events.

@Billy-
Copy link

Billy- commented Feb 12, 2020

@nfiacco that commit is from 2018, this issue was opened in 2014, so on the surface that doesn't add up... Unless the cause now is different to when the issue was originally opened?

@wineSu
Copy link

wineSu commented Sep 1, 2020

I came back from 2022

@laukstein
Copy link

This is old Chrome bug, I just open https://bugs.chromium.org/p/chromium/issues/detail?id=1166619 for it.

@loburets
Copy link

loburets commented Jan 31, 2022

I did a workaround this way in my app:

  const [wasInitiallyAutofilled, setWasInitiallyAutofilled] = useState(false)

  useLayoutEffect(() => {
    /**
     * The field can be prefilled on the very first page loading by the browser
     * By the security reason browser limits access to the field value from JS level and the value becomes available
     * only after first user interaction with the page
     * So, even if the Formik thinks that the field is not touched by user and empty,
     * it actually can have some value, so we should process this edge case in the form logic
     */
    const checkAutofilled = () => {
      const autofilled = !!document.getElementById('field')?.matches('*:-webkit-autofill')
      setWasInitiallyAutofilled(autofilled)
    }
    // The time when it's ready is not very stable, so check few times
    setTimeout(checkAutofilled, 500)
    setTimeout(checkAutofilled, 1000)
    setTimeout(checkAutofilled, 2000)
  }, [])


const fieldNotFilledYet = !touched.field && !values.field && !wasInitiallyAutofilled
const disableSubmitButton = !isValid || fieldNotFilledYet || isSubmitting

@Raul140298
Copy link

@loburets Thank you so much! your solution has saved me.

@facebook facebook locked as resolved and limited conversation to collaborators Apr 22, 2022
@facebook facebook unlocked this conversation Apr 22, 2022
@jlanssie
Copy link

jlanssie commented Jan 5, 2023

Opened a bug at Chromium because it is related to Chromium or its Blink Engine.

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

@pzi
Copy link

pzi commented Feb 13, 2023

Note that @loburets solution triggers a React warning when using Server-Side-Rendering. Have to switch to useEffect or implement it in a different way: https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85

@Murchyman
Copy link

a note for anyone who is having the same issue as me, I had the onchange event modify a value in an object, this did not work, for some reason when I made the value I wanted to change it's own state it worked, maybe this had to do with trying to change multiple values in an object at the same time when doing multiple autofill, not good enough yet to know for sure

@rgdigital
Copy link

We use react-hook-form (in Next.js) and leverage the onChange event on a controlled input component to focus -> blur the input, which triggers validation:

import React, { useEffect, useState, useRef } from "react"

export default function Text({
    id,
    useForm=undefined,
    validation={},
    className="",
    placeholder="",
    type="text",
    value="",
    onChange=undefined,
    ...props
}) {
    
    const { register, control, setValue, trigger, getValues, watch } = useForm;
    
    // Inherit validated error state
    const errors = useForm.formState.errors;

    // Value state
    const [valueState, setValueState] = useState(value ?? "")

    // Input change handler
    const onChangeHandler = (e) => {
        // Fix for autofill
        e.target.focus()
        e.target.blur()
        setValueState(e.target.value)
        onChange && onChange(e.target.value)
    }

    // Parent change handler
    useEffect(() => {
        setValueState(value)
    }, [value])
    
    return (
        <div className={"flex flex-col " + className}>
            <input
                {...register && register(id, { ...validation })}
                className={"p-2.5"}
                placeholder={placeholder}
                type={type}
                value={valueState}
                onChange={onChangeHandler}
            />
            {errors[id] && (<p className="text-sm text-red py-2">{errors[id].message}</p>)}
        </div>
    );
}

NOTE: For the example above the useForm hook is deconstructed in each child component, but fired in the parent, eg:

export default function Page() {
    const form = useForm()

    const onSubmit = (data, e) => {
        console.log(data, e)
    }

    return <form onSubmit={form.handleSubmit(onSubmit)}>
        <Text
            id="email"
            useForm={form}
            placeholder="Email"
            validation={{required: 'Email is required'}}
        />
       <input type="submit" />
    <form>
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests