在React ES6中,为什么在输入一个字符后输入框会失去焦点?

235
在我的下面组件中,在键入字符后,输入字段会失去焦点。使用Chrome Inspector时,看起来整个表单在键入时被重新渲染,而不仅仅是输入字段的值属性。
我从eslint和Chrome Inspector都没有收到错误。
提交表单本身正常工作,实际输入字段也正常工作,当它位于渲染的返回值中或作为单独组件导入时,但在下面所编码的方式中却不行。
为什么会这样? 主页面组件
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actionPost from '../redux/action/actionPost';
import InputText from './form/InputText';
import InputSubmit from './form/InputSubmit';

class _PostSingle extends Component {
    constructor(props, context) {
        super(props, context);
        this.state = {
            post: {
                title: '',
            },
        };
        this.onChange = this.onChange.bind(this);
        this.onSubmit = this.onSubmit.bind(this);
    }
    onChange(event) {
        this.setState({
            post: {
                title: event.target.value,
            },
        });
    }
    onSubmit(event) {
        event.preventDefault();
        this.props.actions.postCreate(this.state.post);
        this.setState({
            post: {
                title: '',
            },
        });
    }
    render() {
        const onChange = this.onChange;
        const onSubmit = this.onSubmit;
        const valueTitle = this.state.post.title;
        const FormPostSingle = () => (
            <form onSubmit={onSubmit}>
                <InputText name="title" label="Title" placeholder="Enter a title" onChange={onChange} value={valueTitle} />
                <InputSubmit name="Save" />
            </form>
        );
        return (
            <main id="main" role="main">
                <div className="container-fluid">
                    <FormPostSingle />
                </div>
            </main>
        );
    }
}

_PostSingle.propTypes = {
    actions: PropTypes.objectOf(PropTypes.func).isRequired,
};

function mapStateToProps(state) {
    return {
        posts: state.posts,
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(actionPost, dispatch),
    };
}

export default connect(mapStateToProps, mapDispatchToProps)(_PostSingle);

文本输入组件

import React, { PropTypes } from 'react';

const InputText = ({ name, label, placeholder, onChange, value, error }) => {
    const fieldClass = 'form-control input-lg';
    let wrapperClass = 'form-group';
    if (error && error.length > 0) {
        wrapperClass += ' has-error';
    }
    return (
        <div className={wrapperClass}>
            <label htmlFor={name} className="sr-only">{label}</label>
            <input type="text" id={name} name={name} placeholder={placeholder} onChange={onChange} value={value} className={fieldClass} />
            {error &&
                <div className="alert alert-danger">{error}</div>
            }
        </div>
    );
};

InputText.propTypes = {
    name: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    placeholder: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired,
    value: PropTypes.string,
    error: PropTypes.string,
};

InputText.defaultProps = {
    value: null,
    error: null,
};

export default InputText;

提交按钮组件

import React, { PropTypes } from 'react';

const InputSubmit = ({ name }) => {
    const fieldClass = 'btn btn-primary btn-lg';
    return (
        <input type="submit" value={name} className={fieldClass} />
    );
};

InputSubmit.propTypes = {
    name: PropTypes.string,
};

InputSubmit.defaultProps = {
    name: 'Submit',
};

export default InputSubmit;

1
感谢这个问题,它是从 https://www.paypal-community.com/t5/Managing-Account/Enter-your-code-page-input-looses-focus/m-p/2876787/highlight/true#M21997 链接过来的,因为这可能与为什么 PayPal 的双重认证开始失去焦点有关... - Tobias Kienzler
34个回答

1

再添加一个答案:当在另一个组件中返回高阶组件时,我也遇到了这个问题。例如,不是:

/* A function that makes a higher order component */
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
    const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
        const { awesomeThing, ...passThroughProps } = props;    

        return (
            <strong>Look at: {awesomeThing}!</strong>
            <Component {...passThroughProps} />
        );

    }

    return AwesomeComponent;
}

/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {

    const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> = 
        makeMyAwesomeHocComponent(TextInput);

    return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}


将创建高阶组件的调用移出你要渲染的组件。
const makeMyAwesomeHocComponent = <P, >(Component: React.FC<P>) => {
    const AwesomeComponent: React.FC<P & AwesomeProp> = (props) => {
        const { awesomeThing, ...passThroughProps } = props;    

        return (
            <strong>Look at: {awesomeThing}!</strong>
            <Component {...passThroughProps} />
        );

    }

    return AwesomeComponent;
}

/* We moved this declaration */
const MyAwesomeComponent: React.FC<TextInputProps & AwesomeProp> = 
    makeMyAwesomeHocComponent(TextInput);

/* The form we want to render */
const MyForm: React.FC<{}> = (props) => {
    return <MyAwesomeComponent awesomeThing={"cat"} onChange={() => { /* whatever */ }} />
}


0

设置正确的ID,确保没有其他组件具有相同的ID,使其唯一,并且在状态更新时不应更改,最常见的错误是在状态更新时更新具有更改值的ID。


0
我曾遇到过这个问题,原因是由于react-bootstrap/Container,一旦我摆脱了它,并为每个表单元素包括了唯一的key,一切都正常了。

0

我今天遇到了这个问题,我知道原因,并且对于在这里的任何人,我建议使用和上面建议的onSubmit()。然而,在我的情况下,这将是过度杀伤力,所以我需要一个简单的解决方案。我决定使用引用

const commentRef = React.useRef<HTMLTextAreaElement>(null);

然后在渲染中使用。

<Textarea ref={commentRef} defaultValue = {someobj.comment} />

在活动期间获取价值,我使用了

commentRef?.current?.value

它有效。不过,如果你能使用更简洁的表单提交解决方案就好了。

0

这可能有点傻,但是……你(读者,不是 OP)是否曾经设置过 disabled={true}

这是一个有点傻的贡献,但我遇到了一个非常类似于本页面所讨论的问题。我在一个组件中放置了一个 <textarea> 元素,当防抖函数结束时,它会失去焦点。

后来我意识到我走错了路。每当自动保存功能触发时,我会将 <textarea> 设置为 disabled={true},因为我不想让用户在保存工作时编辑输入内容。

<textarea> 被设置为禁用时,无论你尝试什么技巧,它都会失去焦点。

我意识到让用户在保存时继续编辑他们的输入内容并没有任何危害,所以我将其删除了。

万一还有其他人也在做同样的事情,那么这可能就是你的问题。即使是有 5 年 React 经验的高级工程师也会做一些愚蠢的事情。


0

我是React的新手,在做项目时遇到了类似的问题,最后通过将新组件放在旧组件外部解决了它。让我向你展示一下:

构建组件的错误方式:

function TodoList() {
  const initialTasks = [
    { id: 0, title: "Make Bed", completed: false },
    { id: 1, title: "Brush Teeth", completed: false },
    { id: 2, title: "Workout", completed: false },
    { id: 3, title: "Plan your day", completed: true },
    { id: 4, title: "Brew coffee", completed: false },
    { id: 5, title: "Login to work", completed: false },
  ];
  const [taskList, setTaskList] = useState(initialTasks);

function AddTaskForm({ taskList, onShow }) {
  const [newTask, setNewTask] = useState("");

  function handleAddTask(event) {
    event.preventDefault();

    var newList = taskList.map((task) => {
      return task;
    });
    if (newTask !== "") {
      var temp = { id: taskList.length + 1, title: newTask, completed: false };
      newList.push(temp);
      onShow(newList);
    }
  }

  return (
    <>
      <form onSubmit={handleAddTask}>
        <input
          name="addtask"
          type="text"
          className="form-control"
          id="addtask"
          aria-describedby="addtask"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <br />
        <button type="submit" className="btn btn-primary">
          Submit
        </button>
      </form>
    </>
  );
}

return (
    <div className="container">
      <AddTaskForm key="random1" taskList={taskList} onShow={setTaskList} />
      <h2>Incomplete Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={false} />
        ))}
      </ul>
      <br />
      <h2>Completed Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={true} />
        ))}
      </ul>
      <br />
    </div>
  );
}

如果您注意上面的代码,我已将AddTaskForm组件添加到TodoList组件中,这在React中是不被允许的。因此,正确的做法是:

function AddTaskForm({ taskList, onShow }) {
  const [newTask, setNewTask] = useState("");

  function handleAddTask(event) {
    event.preventDefault();

    var newList = taskList.map((task) => {
      return task;
    });
    if (newTask !== "") {
      var temp = { id: taskList.length + 1, title: newTask, completed: false };
      newList.push(temp);
      onShow(newList);
    }
  }

  return (
    <>
      <form onSubmit={handleAddTask}>
        <input
          name="addtask"
          type="text"
          className="form-control"
          id="addtask"
          aria-describedby="addtask"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <br />
        <button type="submit" className="btn btn-primary">
          Submit
        </button>
      </form>
    </>
  );
}

function TodoList() {
  const initialTasks = [
    { id: 0, title: "Make Bed", completed: false },
    { id: 1, title: "Brush Teeth", completed: false },
    { id: 2, title: "Workout", completed: false },
    { id: 3, title: "Plan your day", completed: true },
    { id: 4, title: "Brew coffee", completed: false },
    { id: 5, title: "Login to work", completed: false },
  ];
  const [taskList, setTaskList] = useState(initialTasks);

  function handleClick(id) {
    var newList = taskList.map((task) => {
      if (task.id === id) {
        task.completed = !task.completed;
      }

      return task;
    });

    setTaskList(newList);
    console.log(newList);
  }

  return (
    <div className="container">
      <AddTaskForm key="random1" taskList={taskList} onShow={setTaskList} />
      <h2>Incomplete Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={false} />
        ))}
      </ul>
      <br />
      <h2>Completed Tasks</h2>
      <ul className="list-group">
        {taskList.map((task, index) => (
          <Task key={task.id} task={task} completed={true} />
        ))}
      </ul>
      <br />
    </div>
  );
}

记得将组件分开,这样它就不会从输入字段失去焦点。希望这能帮助到某些人,如果我漏掉了什么,请编辑它。


0
这个问题让我困扰了一会儿。由于我使用的是Material UI,我尝试使用Material UI的styled() API自定义表单中的一个包装器组件。问题是由于在渲染函数体内定义DOM自定义函数所导致的。当我将其从函数体中移除后,它就像魔术般地解决了问题。因此,我的观察是,每当我更新状态时,它显然会尝试刷新DOM树并重新声明在渲染函数体内的styled()函数,这为该包装器的DOM元素提供了一个全新的引用,导致该元素失去焦点。这只是我的猜测,请指正我如果我错了。

因此,将styled()实现从渲染函数体中移除对我来说解决了这个问题。


0
如果需要动态添加和删除多个字段,可以使用自动对焦。但是你必须自己跟踪焦点。大致如下:
focusedElement = document.activeElement.id;
[…]

const id = 'dynamicField123'; // dynamically created.
<Input id={id} key={id} {...(focusedElement === id ? { autoFocus: true } : {})} />

0

对于React Native中遇到的问题,即在输入单个字符后文本输入框失去焦点的情况,请尝试将onChangeText传递给TextInput组件。

const [value, setValue] = useState("")
const onChangeText = (text) => {
      setValue(text)
}
return <TextInput value={value} onChangeText={onChangeText} />

0

我使用了 useRefuseEffect 来实现。

对于我来说,这是在 Material UI Tabs 中发生的。我有一个搜索输入过滤器,用于过滤下面的表格记录。搜索输入和表格都在选项卡内部,每当键入字符时,输入框就会失去焦点(因为重新渲染整个选项卡内的内容)。

我使用了 useRef 钩子来引用输入字段,并在我的 useEffect 中触发了输入框的 focus,每当数据列表发生变化时。请参见下面的代码:

const searchInput = useRef();

useEffect(() => {
    searchInput.current.focus();
}, [successfulorderReport]);

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