You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React函数式组件输入字符时输入框焦点丢失问题解决方案

问题根因

你将LoginForm和RegisterForm两个组件定义在了AuthPage组件的内部,输入内容触发setForm更新状态时,AuthPage会重新渲染,内部定义的两个表单组件会被完全销毁重建,对应的DOM节点重新生成,输入框的焦点也就丢失了。

修复方案

两种方案都基于纯函数组件实现,无需使用类组件:

方案1:将子组件移到AuthPage外部(推荐)

把两个表单组件从AuthPage内部挪到外层定义,需要用到的状态、方法通过props传递,示例修改代码如下:

import React, { useContext, useState } from 'react'
import { AuthContext } from '../context/AuthContext'
import axios from 'axios';

// 登录表单移到外部
const LoginForm = ({form, changeHandler, switchToRegister, loginHandler}) => {
  return (
    <div id="logreg-form-container">
      <h1 id="form-header">Log in</h1>
      <div className="label-logreg-input">
        <div className="logreg-labels">
          <label htmlFor="login-email" id="login-label">Email</label>
        </div>
        <input
          placeholder="Email"
          type="text"
          id="login-email"
          name="email"
          value={form.email}
          onChange={changeHandler}
          required />
      </div>
      <div className="label-logreg-input">
        <div className="logreg-labels">
          <label htmlFor="login-password" id="password-label">Password</label>
        </div>
        <input
          placeholder="Password"
          type="password"
          id="login-password"
          name="password"
          value={form.password}
          onChange={changeHandler}
          required />
        <div id="logreg-button-container">
          <button
            onClick={loginHandler}
            id="logreg-form-button"
          >Login</button>
        </div>
      </div>
      <div id="go-to-else-container">
        <a id="go-to-else"
          onClick={(e) => {e.preventDefault(); switchToRegister('register');}}>Register</a>
      </div>
    </div>
  )
}

// 注册表单移到外部
const RegisterForm = ({form, changeHandler, switchToLogin, registerHandler}) => {
  return (
    <div id="logreg-form-container">
      <h1 id="form-header">Register</h1>
      <div className="label-logreg-input">
        <div className="logreg-labels">
          <label htmlFor="register-email" id="login-label">Email</label>
        </div>
        <input
          placeholder="Введите email"
          value={form.email}
          onChange={changeHandler}
          type="text"
          id="register-email"
          name="email"
          required />
      </div>
      <div className="label-logreg-input">
        <div className="logreg-labels">
          <label htmlFor="register-password" id="password-label">Password</label>
        </div>
        <input
          placeholder="Введите пароль"
          value={form.password}
          onChange={changeHandler}
          type="password"
          id="register-password"
          name="password"
          required />
        <div id="logreg-button-container">
          <button
            type="button"
            onClick={registerHandler}
            id="logreg-form-button"
          >Register</button>
        </div>
      </div>
      <div id="go-to-else-container">
        <a id="go-to-else"
          onClick={(e) => {e.preventDefault(); switchToLogin('login');}}>Login</a>
      </div>
    </div>
  )
}

export const AuthPage = () => {
  const auth = useContext(AuthContext)
  const [typeOfForm, setTypeOfForm] = useState('login')
  const [form, setForm] = useState({
    email: '', password: ''
  })
  const loginHandler = async () => {
    try {
      axios.post('http://localhost:5000/api/login', { ...form }, { headers: { 'Authorization': `Bearer ${auth.token}` } })
        .then(response => response.data.map(part => { auth.login(part.token, part.userId); console.log(part) }))
        .catch(function (error) {
          console.log(error);
        });
    } catch (e) { }
  }

  const registerHandler = async () => {
    try {
      axios.post('http://localhost:5000/api/register', { ...form })
        .then(response => response.data.map(part => { auth.login(part.token, part.userId); console.log(part) }))
        .catch(function (error) {
          console.log(error);
        });
    } catch (e) { }
  }

  const changeHandler = event => {
    // 改用函数式更新避免闭包问题
    setForm(prev => ({ ...prev, [event.target.name]: event.target.value }))
  }


  return (
    <div className="App" key="editor2">
      <div className="logreg-bg"></div>
      <a href="/">
        <h1 id="logreg-logo">Animal Assistance <br /> <span id="logreg-logo-down">save nature togehter</span></h1>
      </a>
      {typeOfForm == 'login' 
        ? <LoginForm 
            form={form} 
            changeHandler={changeHandler} 
            switchToRegister={setTypeOfForm}
            loginHandler={loginHandler}
          /> 
        : <RegisterForm 
            form={form} 
            changeHandler={changeHandler} 
            switchToLogin={setTypeOfForm}
            registerHandler={registerHandler}
          />}
      <h1 id="logreg-logo2"> Created by: <br /> <span id="logreg-logo-down2">PTN</span></h1>
    </div>
  );
}

export default AuthPage;

方案2:内联表单JSX

如果不想拆分组件,可以直接把两个表单的JSX写在AuthPage的return语句中,不用封装为内部的函数组件,也能避免组件重建导致的焦点丢失问题。

其他注意事项
  • 原代码中登录表单的提交按钮错误绑定了registerHandler,已在示例中修正
  • 原代码重复使用了相同的id(如label-logreg-input、login、password),不符合HTML规范,示例中已调整为类名或区分了不同id
  • 切换表单的a标签增加了preventDefault阻止默认跳转行为,避免页面刷新
  • setForm改用函数式更新写法,避免闭包导致的状态更新异常

内容的提问来源于stack exchange,提问作者n33t

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 08:24:03