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
相关产品推荐
相关产品推荐

