React Hooks - 当输入一个字符时,输入框会失去焦点。

41

我正在使用React Hooks编写一个表单,一切都按预期工作,除了当我在输入框中输入任何一个字符后,输入框会失去焦点。

我猜这是因为组件外部不知道组件内部的变化所导致的问题,但是我该如何解决这个问题呢?

以下是useForm Hook的代码:

import React, { useState } from "react";

export default function useForm(defaultState, label) {
  const [state, setState] = useState(defaultState);

  const FormComponent = () => (
    <form>
      <label htmlFor={label}>
        {label}
        <input
          type="text"
          id={label}
          value={state}
          placeholder={label}
          onChange={e => setState(e.target.value)}
        />
      </label>
    </form>
  );

  return [state, FormComponent, setState];
}

这是使用 Hook 的组件:

function App() {
  const [formValue, Form, setFormValue] = useForm("San Francisco, CA", "Location");

  return (
    <Fragment>
      <h1>{formValue}</h1>
      <Form />
    </Fragment>
  );
}
8个回答

54
虽然Kais的答案可以解决症状,但它没有解决问题的根本原因。如果有多个输入,它也会失败 - 那么应该将焦点自动对准哪一个重新渲染呢?
当您在另一个函数的作用域内定义一个组件(FormComponent),并且每次渲染App组件时调用该函数时,就会出现此问题。这样,每次重新渲染App组件并调用useState时,您都会得到一个全新的FormComponent。那个新组件就是没有焦点的。
个人而言,我不赞成从钩子中返回组件。我更倾向于定义一个FormComponent组件,并仅从useForm状态返回状态。
但是,最接近您原始代码的工作示例可能是:
// useForm.js
import React, { useState } from "react";

// Define the FormComponent outside of your useForm hook
const FormComponent = ({ setState, state, label }) => (
  <form>
    <label htmlFor={label}>
      {label}
      <input
        type="text"
        id={label}
        value={state}
        placeholder={label}
        onChange={e => setState(e.target.value)}
      />
    </label>
  </form>
);

export default function useForm(defaultState, label) {
  const [state, setState] = useState(defaultState);

  return [
    state,
    <FormComponent state={state} setState={setState} label={label} />,
    setState
  ];
}

// App.js
import useForm from "./useForm";

export default function App() {
  const [formValue, Form] = useForm("San Francisco, CA", "Location");

  return (
    <>
      <h1>{formValue}</h1>
      {Form}
    </>
  );
}

这是一个沙盒


3
这应该是答案。 - Akash Sarkar
1
这真的有效!但我不明白为什么。请问,您能解释一下为什么您的解决方案有效吗? - Alfonso Tienda
3
由于我将 FormComponent 移到了 useForm 函数外部,所以它始终指向同一个对象(与每次调用 useForm 函数创建新对象的情况相反),这样 React 不再确定需要完全重新绘制 FormComponent 的整个 DOM 表示形式(对象是相同的,除了输入值之外没有更改)。因此,“isFocused”或类似的标志在重新绘制期间不再丢失。请记住,每次用户输入内容时,React 都会使用更新后的状态调用 useForm。至少我是这样理解的。 - Kasparas Anusauskas
@PredragDavidovic 您的用例是正确的(实际上大多数情况下都是这样做的),因为您没有在父组件中创建输入/表单组件。因此,重新渲染不会在每次渲染时创建新的输入/表单组件实例。在这里,我打破了您的示例,以便您可以看到原始问题所在(有点像使用钩子,但核心问题是相同的):https://codesandbox.io/s/parent-child-do-not-lose-focus-forked-nqeqi - Kasparas Anusauskas
嗨,我理解了这个问题,并在我的应用程序中修复了它。有趣的是,如果你在 CodeSandbox 中重现这些问题,它将完美地工作,但当你在本地运行它时,它会失去焦点。谢谢 @KasparasAnusauskas - Predrag Davidovic
显示剩余2条评论

25
当你在输入框中输入任何文本时,父组件也会重新渲染。因此,你需要手动聚焦于输入框。 为此,在输入标签中使用autoFocus属性。
<input
  type="text"
  id={label}
  value={state}
  placeholder={label}
  onChange={e => setState(e.target.value)}
  autoFocus
/>

1
到目前为止,这是最好的答案,在我的情况下,我没有任何父组件渲染子组件! - Delcio Polanco
autoFocus 我简直不敢相信这个起作用了.. 不过仍然会出现受控的 React 错误。 - Daniel Ram
这对我来说不起作用。第一个字符后仍然模糊。 - AlxVallejo
1
这在表单中使用多个输入时会出现失焦问题。 - undefined

10

上述答案对我没有用。对我有效的解决方案要简单得多,因此也不那么明显。

问题: 实际上,我正在使用更改的值也被用于一系列key中的每个输入。

因此,当我更新值时,key会改变,React将检测到它相对于上一个键不同并创建一个新的输入。作为新输入,它不会关注自己。

然而,使用autoFocus后,它将自动聚焦于新创建的输入。但是问题并未得到解决,因为很明显该输入框在不断经历失去焦点和获得焦点的周期。

这里有一篇文章演示了这个问题。

解决方案

key更新为不可更改的值,以便React可以稳定地引用列表项。在我的情况下,我只需将其更新为索引即可。这并不理想(React文档建议使用稳定的ID),但在我的情况下,由于项目的顺序不会改变,这是可以的。


1
由于这个解决方案,我开始跳出思维定势。另一个可能表现为毫无意义的父组件重新渲染的问题。在控制台记录下你的组件树,并确保重新渲染只发生在你期望的时候,这样做也许不会有什么坏处!我曾经在组件树的错误“层级”上解决问题。 - Jeff R
1
非常感谢您的帮助。在找到您的答案之前,我不得不阅读五个完全相同的SO问题,而您的答案解决了我的问题。 - undefined
1
这个应该被点赞 - undefined
1
这个解决方案帮助我在很多小时后找到了问题,并得到了赞同。我之前使用的是nanoid()作为key的id生成器。在将其更改为唯一id后,问题得到了解决。 - undefined

0
另一个可能导致问题的原因是在表单验证过程中通过onChange事件禁用了输入字段。
当输入字段被禁用时,它将失去焦点。
在这种情况下,这意味着您的输入失去焦点的原因是由以下过程引起的:
1. 焦点在字段上 2. 开始输入 3. 在输入第一个字符后,验证过程发生 4. 如果您在验证过程中禁用了字段,那么禁用过程将禁用所有输入 5. 输入将失去焦点,因为它被禁用了。
因此,请确保您不会因为任何原因禁用输入字段,即使只是一瞬间,因为这会导致输入失去焦点。

0

第一个解决方案对我实际有效,最初包含文本字段的功能组件是主要功能组件的一部分,我遇到了同样的问题,但当我将文本字段组件提取到另一页并导入它时,它正常工作。这是我的组件

 <Paper >
      <div>
      <div style={{padding:'10px' ,display:'flex'}}  >       
      <inputstyle={{marginRight:'5px'}} value={val} onChange={(e)=>{setVal(e.target.value)}} />             
    </div>
    </div>
    </Paper>

0
我在使用styled-components时遇到了这个问题,问题是每次输入发生变化时,styled components都会更改类,并且类的更改会导致组件重新渲染。
解决方案是像这样在函数组件外部声明styled component。
const FormButton = styled.button``

export default function Button(){
   return(   
      <FormButton>Log In</FormButton>        
)

0

我曾遇到类似的问题,涉及多个输入(可重用自定义组件),我只是简单地给输入添加了 key,问题就解决了。即使有多个输入,这也能解决你的问题。 (这是因为父组件重新渲染时,React 不知道哪个输入元素拥有焦点,因为它们没有通过 key 属性来标识具体的焦点元素。) 请千万不要使用 rand() 函数作为 key id(对于新手而言)。

**例如,由于错误的 key 属性导致焦点错误码**

return(
 <CustomInput key={rand()} placeHolder="email" type="email" data= 
 {onChangeHandler}/>
 <CustomInput key={rand()} placeHolder="password" type="password" 
 data= 
 {onChangeHandler}/>
 <CustomInput key={rand()} placeHolder="name" type="name" data= 
 {onChangeHandler}/>
 <CustomInput key={rand()} placeHolder="age" type="age" data= 
 {onChangeHandler}/>
 )

上述代码的解释。子组件(自定义输入组件)有一个useState,每当用户在输入框中输入时,onChange处理程序会在每次按键时执行,这导致父组件重新渲染,React在重新渲染后感到困惑,因为错误的key props(rand()生成了新的key props,与之前的key prop不同,使React感到困惑,从而导致焦点丢失)。
以下是正确的代码。
return(
 <CustomInput key="id1" placeHolder="email" type="email" data= 
 {onChangeHandler}/>
 <CustomInput key="id2" placeHolder="password" type="password" 
 data= 
 {onChangeHandler}/>
 <CustomInput key="id3" placeHolder="name" type="name" data= 
 {onChangeHandler}/>
 <CustomInput key="id4" placeHolder="age" type="age" data= 
 {onChangeHandler}/>
 )

上述代码的解释:现在在将键更改为固定ID后,React将知道在父组件重新渲染后将焦点放回到正确的输入框上,因为key属性(例如"id1")在重新渲染后仍然相同。

欢迎来到Stack Overflow :) 只是想告诉你,如果提供一个例子或者链接到示例的话,会大大提高这个答案的可读性。 - Gander7
1
你的回答可以通过提供更多支持性信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人能够确认你的回答是否正确。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - Community
@Gander7 请立即审核。 - Abdul Moiz Iqbal

0
你可以使用react-hook-forms中的Controller,渲染元素并传递value、onChange和ref给它。
<Controller
        control={control}
        rules={{
            maxLength: 100,
        }}
        render={({ field: { onChange, onBlur, value, ref } }) => (
            <TextInput
                ref={ref}
                placeholder="Last name"
                onBlur={onBlur}
                onChangeText={onChange}
                value={value}
            />
        )}
        name="lastName"
    />

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