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

React表单onChange触发setValues后重载,无法修改字段求助

问题描述

创建可复用表单字段时,触发onChange执行setValues后,表单出现类似重载的现象,无法继续修改字段。相关代码如下:

BuildForm.js(自定义Hook文件)

import { useState } from 'react';

const BuildForm = () => {
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});

  const validate = (name, value) => {
    switch (name) {
      case 'first_name':
            if(value.trim() === ''){
              errors[name] = 'First Name is Rrequired.';
              setErrors(errors);
            }
            else{
              errors[name] = '';
              setErrors(errors);
            }
        break;
      case 'last_name':
            if(value.trim() === ''){ setErrors({...errors, [name]:'Last Name is Rrequired.'}); }
            else{
               setErrors({...errors, [name]:''});
             }
        break;
      default:
        break;
    }
  };
  const handleChange = (event) => {
      // event.persist();
      let name = event.target.name;
      let value = event.target.value;
      validate(name, value);
      setValues({...values, [name]:value});  // this is line with the issue
      console.log(name, value);
  };

  const Name = (props) => {
    return(
      <div className="fullwidth">
        <p className="description"><strong>{props.label}</strong></p>
        <div className="firstname halfwidth left">
          <label htmlFor="first_name">FIRST</label>
          <input type="text" name="first_name" onChange={handleChange}/>
          { errors.first_name && <p className="validation">{errors.first_name}</p> }
        </div>
        <div className="lastname halfwidth right">
          <label htmlFor="last_name">LAST</label>
          <input type="text" name="last_name" onChange={handleChange} />
          { errors.last_name && <p className="validation">{errors.last_name}</p> }
        </div>
      </div>
    );
  };

  return {
    Name,
  }
};

export default BuildForm;

FirstForm.js(表单页面文件)

import './App.css';
import React, {useState} from 'react';
import { Link, Routes, Route, useNavigate } from 'react-router-dom';
import BuildForm from './Hooks/BuildForm';

function FirstForm() {
  const navigate = useNavigate();
  const MyForm = BuildForm();

  return (
    <div className="App">
      <div id="header">
        <img src={logo} className="logo" alt="logo"/>
      </div>
      <div id="pagebody">
        <div id="formcontainer">
          <form id="myForm">
            <MyForm.Name label="Client Name"/>
            <div className="submitbutton fullwidth"><input type="submit" /></div>
          </form>
        </div>
      </div>
    </div>
  );
}

export default FirstForm;
问题原因
  1. 组件定义在Hook内部:Name组件被定义在BuildForm Hook内部,每次Hook的状态(values/errors)更新时,Hook会重新执行,生成全新的Name组件。React会将其识别为不同的组件类型,卸载旧组件并挂载新组件,导致输入框丢失焦点、状态重置,无法继续输入。
  2. 状态更新不规范:first_name校验逻辑中直接修改errors状态对象后调用setErrors,未使用不可变更新,React无法正确检测状态变化,可能引发渲染异常。
  3. 未阻止表单默认提交:表单提交按钮未阻止默认行为,可能触发页面刷新,表现为表单重载。
修复方案

以下是修复后的完整代码:

修复后的BuildForm.js

import { useState, useCallback, useMemo } from 'react';

const BuildForm = () => {
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});

  // 用useCallback缓存校验函数,避免不必要重渲染
  const validate = useCallback((name, value) => {
    // 不可变更新errors,创建新对象
    const newErrors = { ...errors };
    switch (name) {
      case 'first_name':
        newErrors[name] = value.trim() === '' ? 'First Name is Required.' : '';
        break;
      case 'last_name':
        newErrors[name] = value.trim() === '' ? 'Last Name is Required.' : '';
        break;
      default:
        break;
    }
    setErrors(newErrors);
  }, [errors]);

  // 用useCallback缓存handleChange,确保引用稳定
  const handleChange = useCallback((event) => {
    const { name, value } = event.target;
    validate(name, value);
    // 函数式更新values,确保获取最新状态
    setValues(prevValues => ({ ...prevValues, [name]: value }));
    console.log(name, value);
  }, [validate]);

  // 用useMemo缓存Name组件,确保组件引用不变
  const Name = useMemo(() => (props) => {
    return(
      <div className="fullwidth">
        <p className="description"><strong>{props.label}</strong></p>
        <div className="firstname halfwidth left">
          <label htmlFor="first_name">FIRST</label>
          <input type="text" name="first_name" onChange={handleChange}/>
          { errors.first_name && <p className="validation">{errors.first_name}</p> }
        </div>
        <div className="lastname halfwidth right">
          <label htmlFor="last_name">LAST</label>
          <input type="text" name="last_name" onChange={handleChange} />
          { errors.last_name && <p className="validation">{errors.last_name}</p> }
        </div>
      </div>
    );
  }, [handleChange, errors]);

  return {
    Name,
  }
};

export default BuildForm;

修复后的FirstForm.js

import './App.css';
import React from 'react';
import { useNavigate } from 'react-router-dom';
import BuildForm from './Hooks/BuildForm';

function FirstForm() {
  const navigate = useNavigate();
  // 直接解构获取Name组件,避免多余的对象包装
  const { Name } = BuildForm();

  // 阻止表单默认提交行为,避免页面刷新
  const handleSubmit = (e) => {
    e.preventDefault();
    // 可在此添加表单提交逻辑
  };

  return (
    <div className="App">
      <div id="header">
        <img src={logo} className="logo" alt="logo"/>
      </div>
      <div id="pagebody">
        <div id="formcontainer">
          <form id="myForm" onSubmit={handleSubmit}>
            <Name label="Client Name"/>
            <div className="submitbutton fullwidth"><input type="submit" /></div>
          </form>
        </div>
      </div>
    </div>
  );
}

export default FirstForm;

关键修复点说明

  • 缓存组件与回调:通过useMemo缓存Name组件,确保Hook状态更新时组件引用稳定,避免React卸载重建;用useCallback缓存handleChange和validate,减少不必要的重渲染。
  • 不可变状态更新:校验函数中通过展开运算符创建新的errors对象,避免直接修改原状态;setValues使用函数式更新,确保获取最新的状态值。
  • 阻止表单默认提交:添加handleSubmit函数阻止表单默认刷新行为,避免页面重载。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 01:10:27