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

React.js输入框每输入一个字符就失焦问题如何解决?

问题根因

你在Auth组件内部嵌套定义了Emailform、Passform、ConfirmPass、AuthType、Login、Signup等子组件。每次输入触发setEmail/setPassword更新Auth的状态时,整个Auth组件会重渲染,所有嵌套定义的子组件都会被重新构造为全新的函数,React渲染时会判定为不同的组件类型,直接销毁旧组件实例、挂载新的实例,输入框自然就会失去焦点,和onChange逻辑本身无关。

修复方案

方案1(推荐,符合React规范)

把所有嵌套在Auth内部的子组件全部移到Auth组件外部定义,需要用到的父组件状态、方法通过props传递即可,同时修复两个附带问题:

  1. Form组件参数调用错误:你在Signup中给Form传的是type属性,但组件内部读取的是props.input,会导致输入框type失效
  2. useClickOutside的useEffect缺少空依赖数组,会导致每次渲染都重复绑定/解绑全局点击事件

核心修正代码示例

import style from "./auth.module.css";
import { useEffect, useRef, useState } from "react";
import { useAuthState } from 'react-firebase-hooks/auth';
import { auth, signInWithEmailAndPassword, signInWithGoogle, registerWithEmailAndPassword } from "../../firebase";
import { CSSTransition } from "react-transition-group";

// 所有子组件移到Auth外部定义
const Emailform = ({ value, onChange }) => {
  return (
    <div className={style.formBox}>
      <label className={style.label}>Email:</label>
      <form className={style.input}>
        <input 
          type="email" 
          name="email" 
          required
          value={value}
          onChange={onChange}
        />
      </form>
    </div>
  );
}

const Passform = ({ value, onChange }) => {
  return (
    <div className={style.formBox}>
      <label className={style.label}>Password:</label>
      <form className={style.input}>
        <input 
          type="password" 
          name="password" 
          required
          value={value}
          onChange={onChange}
        />
      </form>
    </div>
  );
}

const ConfirmPass = () => {
  return (
    <div className={style.formBox}>
      <label className={style.label}>Confirm Password:</label>
      <form className={style.input}>
        <input 
          type="password" 
          name="password" 
          required
        />
      </form>
    </div>
  );
}

const AuthType = ({ goToMenu, setActiveMenu, children }) => {
  return (
    <a
      className={style.menuItem}
      onClick={(e) => {
        e.preventDefault();
        goToMenu && setActiveMenu(goToMenu);
      }}
    >
      {children}
    </a>
  );
}

const Login = ({ activeMenu, domNode, email, password, setEmail, setPassword, setActiveMenu }) => {
  return (
    <CSSTransition in={activeMenu === "main"} unmountOnExit timeout={500}>
      <div ref={domNode}>
        <div className={style.login}>
          <h1 className={style.title}>Clip It</h1>
          <Emailform value={email} onChange={(e) => setEmail(e.target.value)}/>
          <Passform value={password} onChange={(e) => setPassword(e.target.value)}/>
          <div className={style.button}>
            <input 
              type="submit" 
              value="Login"
              onClick={() => signInWithEmailAndPassword(email, password)} />
            <input 
              type="submit" 
              value="Login with Google"
              onClick={signInWithGoogle} />
          </div>
          <div className={style.text}>
            <p className={style.plink}>Forgot Password</p>
            <div>
              Need an account?&nbsp;
              <AuthType goToMenu="Signup" setActiveMenu={setActiveMenu}>click here</AuthType>
            </div>
          </div>
        </div>
      </div>
    </CSSTransition>
  );
}

const Signup = ({ activeMenu, domNode, email, setEmail, password, setPassword, setActiveMenu }) => {
  return (
    <CSSTransition in={activeMenu === "Signup"} unmountOnExit timeout={500}>
      <div ref={domNode}>
        <div className={style.signUp}>
          <div className={style.title}> Clip It</div>
          <Form label="First Name" type="text" />
          <Form label="Last Name" type="text" />
          <Emailform value={email} onChange={(e) => setEmail(e.target.value)}/>
          <Form label="Date of Birth" type="date" />
          <Passform value={password} onChange={(e) => setPassword(e.target.value)}/>
          <ConfirmPass />
          <div className={style.button}>
            <input 
              type="submit" 
              value="Sign Up"
              onClick={registerWithEmailAndPassword} />
          </div>
          <div className={style.text}>
            have an&nbsp;
            <AuthType goToMenu="main" setActiveMenu={setActiveMenu}> account</AuthType>
          </div>
        </div>
      </div>
    </CSSTransition>
  );
}

const Form = (props) => {
  return (
    <div className={style.formBox}>
      <label className={style.label}>{props.label}:</label>
      <form className={style.input}>
        {/* 修正参数读取错误 */}
        <input 
          type={props.type} 
          name={props.label} 
          required
        />
      </form>
    </div>
  );
}

let useClickOutside = (handler) => {
  let domNode = useRef();

  // 加空依赖数组
  useEffect(() => {
    let clickListener = (event) => {
      if (domNode.current && !domNode.current.contains(event.target)) {
        handler();
      }
    };
    document.addEventListener("mousedown", clickListener);
    return () => {
      document.removeEventListener("mousedown", clickListener);
    };
  }, []);
  return domNode;
};

const Auth = () => {
  const [activeMenu, setActiveMenu] = useState("main");
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [user, loading, error] = useAuthState(auth);

  let domNode = useClickOutside(() => {
    setActiveMenu("main");
  });

  return (
    <div className={style.container}>
      {/* 传所需props */}
      <Login 
        activeMenu={activeMenu}
        domNode={domNode}
        email={email}
        password={password}
        setEmail={setEmail}
        setPassword={setPassword}
        setActiveMenu={setActiveMenu}
      />
      <Signup 
        activeMenu={activeMenu}
        domNode={domNode}
        email={email}
        password={password}
        setEmail={setEmail}
        setPassword={setPassword}
        setActiveMenu={setActiveMenu}
      />
    </div>
  );
}

export default Auth;

方案2(临时快速修复)

如果不想调整组件位置,可以把子组件的调用方式从组件调用改为普通函数调用,比如把<Emailform/>改成{Emailform()},<Login/>改成{Login()},避免React把它们判定为不同的组件类型,但这种方式不推荐长期使用,会影响组件的性能优化空间。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 15:36:03