自动填充不触发onChange事件

49

我有一个登录表单,当页面打开时,Chrome会自动填写凭据。然而,input元素的onChange事件并没有被触发。

点击页面上任何位置都会将它们注册到inputvalue属性中,这正是我们需要的。我尝试使用event.target.click(),但没起作用。

onChange中,我将值设置到状态(state)中:

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

如果email.length === 0,则提交按钮将被禁用。

因此,即使页面显示凭据已填写,提交按钮仍然看起来是禁用的。

由于单击任何地方都会注册值,单击提交按钮也会在提交之前注册它们。 因此,它运行得很完美。

这不是关于时间的问题,该字段永远不会填充。 当我打开开发人员控制台进行检查时,值字段为空,直到我单击某个位置。

这里唯一的问题是,按钮看起来被禁用,因为除非单击某个东西,否则电子邮件输入值为空。 是否有一个巧妙的解决方案?


你能贴一个 jsfiddle 的链接吗?那会非常有帮助。 - Ashish Santikari
2
你解决过这个问题吗?我也遇到了同样的问题。页面加载后输入框(电子邮件地址)被填充,但是输入框引用的值为空,直到页面与之交互(点击即可),所以第二次渲染就没问题了。需要说明的是,这只在Chrome浏览器中出现,Firefox浏览器正常。 - bsmidtson
只需使用 oninput 和 onchange。 - Jesse Pepper
@bsmidtson 提供了一个 oninput 处理程序以及 onchange,正如第二个答案中建议的那样,这对我很有帮助。应该接受这个答案。 - Jesse Pepper
我知道这不是解决所描述问题的方法,但如果您在提交带有自动填充值的表单时遇到问题,请将焦点放在输入框上。用户单击提交按钮后,浏览器将以某种方式使用onchange/onblur事件注册自动填充的值。 - Tomasz Kleszczewski
显示剩余3条评论
10个回答

40

我遇到了这个问题,因为我也有同样的问题。这不是React特有的问题,而是一种通用问题。也许这会帮助其他遇到这个问题的人。

其他答案都没有真正回答问题。由OP所描述的问题是Chrome在页面没有用户交互之前不设置输入值。 这可能是单击页面,甚至是在控制台中键入任意内容。

在发生用户交互之后,输入的值被设置并触发更改事件。

您甚至可以通过视觉效果看到这一点,因为自动填充文本的样式在首次呈现时不同,并且在交互发生后更改为输入的样式。

另外:从代码触发单击或设置焦点都没有帮助,只有真正的人类交互。

因此,轮询值不是解决方案,因为如果未单击页面,则该值将保持为空。 同样声明不能依赖更改事件也不是很相关,因为事件确实会正确触发,但需要等待值的延迟设置。 没有确定的延迟才是问题。

但是,您可以轮询Chrome的伪CSS类的存在。 在Chrome 83(2020年6月)中,这些是:-internal-autofill-selected:-internal-autofill-previewed。 然而,这些会定期更改。 而且,在呈现后它们并不直接存在。 因此,您应该设置足够的超时时间或轮询。

在vanilla JavaScript中的例子:

var input = document.getElementById('#someinput');

setTimeout(() => {
   if (input.matches(':-internal-autofill-selected')) {
      /* do something */
   }
}, 500);

在这一点上,您仍然无法获取值,因此在这方面它仍然没有解决问题,但至少您可以根据自动填充数据的存在启用提交按钮(正如 OP 想要做的那样)。或者做一些其他视觉效果。


关于Cyber Valdez所说的,使用oninput怎么样?对我来说直接就起作用了。 - Jesse Pepper
1
@JessePepper 不,oninput的行为与onchange相同(目前在Chrome 90上进行了测试),即仅在用户交互后触发(OP的问题)。我的答案仍然是唯一一个解决这个问题并提供某种解决方案的答案。你能否撤销不必要的负评?谢谢。 - Karim Ayachi
我喜欢Karim的这个答案。我要指出,如果您有不同/竞争的“自动填充”行为,请小心。当Google Chrome自动填充正在工作时,我得到了Karim所描述的“字体切换”/等待用户点击。(https://i.stack.imgur.com/uvwqs.gif) 当LastPass扩展自动填充正在工作时,我得到了正确的字体和valueChange事件。(https://i.stack.imgur.com/D3QhJ.gif) - Nate Anderson
此外,让我们记住自动填充与自动完成之间的区别;正如Karim所说,这个问题是关于自动填充(自动完成意味着某些用户交互)。最后,正如Karim指出的那样,CSS伪类可能会发生变化;在2017年,人们似乎会查询CSS伪类:-webkit-autofill(但现在请看Karim对:-internal-autofill-selected的使用)。 - Nate Anderson
1
另外需要注意的一点是,正如Karim所说,即使你对CSS伪类进行轮询/监视,这并不意味着输入具有值(尽管输入看起来有值);Karim表示“你仍然无法获取该值...因此它仍然无法解决问题”。我也经历过这种情况- 我的应用程序需要输入值进行查询。因此,我别无选择,只能等待用户在页面上点击。除非我对[type="search"]进行强制操作(https://dev59.com/Imct5IYBdhLWcg3wEJcm#30873633),否则我将无法成功使用`autocomplete =“off”`来防止自动填充(同时用于用户名和密码)。 - Nate Anderson
3
此外,自动填充的样式与页面上其他“输入”完全不同,直到有人点击页面。多么愚蠢啊。我想知道Chrome当前的开发人员是否还记得他们曾经必须与其他浏览器竞争的时代。 - Emperor Eto

2
答案是要么在你的表单中使用autocomplete="off"来禁用自动完成,要么定期轮询以查看它是否填充。
问题在于不同的浏览器处理自动填充的方式不同。有些分派变更事件,而有些则没有。因此,几乎不可能钩取当浏览器自动完成输入字段时触发的事件。
不同浏览器的更改事件触发:
对于用户名/密码字段:
1. Firefox 4、IE 7 和 IE 8 不分派更改事件。 2. Safari 5 和 Chrome 9 分派更改事件。
对于其他表单字段:
1. IE 7 和 IE 8 不分派更改事件。 2. Firefox 4 在用户从建议列表中选择一个值并离开字段时分派更改事件。 3. Chrome 9 不分派更改事件。 4. Safari 5 分派更改事件。
你最好的选择是使用在表单中使用 autocomplete="off" 禁用自动填充,或定期轮询以查看是否已填充。
关于你是否在 document.ready 前或之后填充的问题,它也因浏览器和版本而异。仅在选择用户名密码字段时才填充用户名密码字段。因此,如果你尝试连接到任何事件,代码会非常混乱。
你可以阅读这个 http://avernet.blogspot.in/2010/11/autocomplete-and-javascript-change.html(不是 https!)

请查看此主题:检测浏览器自动填充


好的阅读链接无法访问。 - Akshay Bheda
轮询不起作用的原因很简单,即在检测到物理点击之前,输入值永远不会被填充。你可以无限期地进行轮询,但最终仍将得到空值。 - AttemptedMastery
这在最近有改变吗? - paul23

2

使用oninput而不是onchange可以解决问题。


2
不知道为什么这不是最佳答案! - Jesse Pepper
23
因为它并不能回答原帖中的问题。oninput事件(就像onchange事件)仅在用户交互后触发。这是原帖中的问题,使用oninput事件无法解决。 - Karim Ayachi
5
无法运行 - duduwe
这对我做的事情是突出显示输入字段,就像它们已经填充了,但值没有显示。 - Sudhir Kaushik

1

event.target.click()将“模拟”点击并触发onClick()事件,但不像在浏览器窗口中的物理点击那样操作。

您是否已尝试使用focus()而不是(或除了)click()?您可以尝试将焦点放在输入元素上,然后检查值是否设置。如果有效,您可以在焦点和单击时添加this.setState({ email: event.target.value })


0
在React存储库的讨论中找到了一个解决方案https://github.com/facebook/react/issues/1159。通过使用超时多次调用检查函数来检查'*:-webkit-autofill'。
const [wasInitiallyAutofilled, setWasInitiallyAutofilled] = useState(false)

useEffect(() => {
    /**
     * 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)
  }, [])

0
不需要触发点击事件,我认为如果你在componentDidMount中设置email状态会更好。
this.setState({ email: document.getElementById('email').value })

一旦调用了componentDidMount(),Chrome会填充电子邮件并更新state,然后呈现提交按钮并使其启用。
您可以在link中阅读有关React生命周期的更多信息。

听起来问题发生在渲染之后,所以检查componentDidMount可能不会有任何区别。希望原帖作者能够参与并给我们一些澄清。 - Ed Lucas
useEffect 中设置调试器后,电子邮件输入框的 .value 为空,并且在 Chrome 填充输入框后,useEffect 不会第二次触发(省略了数组参数)。只有当我与页面交互(导致重新渲染)时,useEffect 才会第二次触发,并且可以检测到由 Chrome 放置在电子邮件字段中的值。 - bsmidtson

0

一种稍微更好(依我个人意见)的解决方案,不需要使用 setTimeout()

为了规避这个问题,您可以为每个 inputonAnimationStart 事件添加一个处理程序,检查 animationName 是否为 "mui-auto-fill",然后检查 input 是否具有 -webkit-autofill 伪类, 以查看浏览器是否自动填充了字段。您还需要处理场景中的 "mui-auto-fill-cancel" 情况,即表单被自动填充并且用户清除了值(以重置 shrink)。

例如:

const [passwordHasValue, setPasswordHasValue] = React.useState(false);

// For re-usability, I made this a function that accepts a useState set function
// and returns a handler for each input to use, since you have at least two 
// TextFields to deal with.
const makeAnimationStartHandler = (stateSetter) => (e) => {
  const autofilled = !!e.target?.matches("*:-webkit-autofill");
  if (e.animationName === "mui-auto-fill") {
    stateSetter(autofilled);
  }

  if (e.animationName === "mui-auto-fill-cancel") {
    stateSetter(autofilled);
  }
};
...

<TextField
  type="password"
  id="password"
  inputProps={{
    onAnimationStart: makeAnimationStartHandler(setPasswordHasValue)
  }}
  InputLabelProps={{
    shrink: passwordHasValue
  }}
  label="Password"
  value={password}
  onChange={(e) => {
    setPassword(e.target.value);
    ...
  }}
/>

结果“on load”应该显示为:

example

更新取消--允许用户在加载自动填充后清除表单字段,并重置标签:

example with reset field

FYI:我将我的makeAnimationStartHandler函数设置为接受一个React.useState()的setter作为参数,并返回一个实际执行工作的处理程序,因为你的示例有两个字段,我想要1)减少代码量和2)允许你仍然单独处理每个字段的手动输入用例,如果需要的话。
工作示例:https://z3vxm7.csb.app/ 工作CodeSandbox:https://codesandbox.io/s/autofill-and-mui-label-shrink-z3vxm7?file=/Demo.tsx

很努力,但你的代码是无效的。 在 CodeSandbox。 在自动填充后,通过按退格键删除一个字符后,输入看起来是空的(但实际上不是)。 - undefined

-1

有同样的问题。我的解决方案是:

function LoginForm(props: Props) {

    const usernameElement = React.useRef<HTMLInputElement>(null);
    const passwordElement = React.useRef<HTMLInputElement>(null);

    const [username, setUsername] = React.useState<string>('');
    const [password, setPassword] = React.useState<string>('');

    React.useEffect(() => {
        //handle autocomplite
        setUsername(usernameElement?.current?.value ?? '');
        setPassword(passwordElement?.current?.value ?? '');
    }, [usernameElement, passwordElement])


    return (
        <form className='loginform'>

            <input ref={usernameElement} defaultValue={username} type='text' onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
                setUsername(event.target.value);
            }} />

            <input ref={passwordElement} defaultValue={password} type='password' onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
                setPassword(event.target.value);
            }} />


            <button type='button' onClick={() => {
                console.log(username, password);
            }}>Login</button>
        </form>
    )
}

-4
useEffect(() => {
  onChange(value);
}, 
[value, onChange]);

那对我有用。


-5

不行,因为 e.target.value 总是为空。这就是问题所在。 - naezith
你尝试过记录事件值吗?你上面的代码片段非常错误。event.target.email 应该是 event.target.value。另外,你能告诉我你在哪个浏览器版本遇到了这个问题吗? - Ashish Santikari
更正了那个拼写错误。Chrome macOS 73.0.3683.75 - naezith
记录哪个事件值?onChange没有被触发,因此没有事件。 - naezith
我使用的是相同版本的 Chrome,且未发现该问题。您是否愿意通过 codesandbox.io 分享您的代码? - Ashish Santikari
请提供更多内容,而不仅仅是一个外部链接,以增加此主题的价值。该链接可能在未来无法访问。最好将您的解决方案简化并复制到您的答案中。 :) - lehm.ro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接