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

React自定义表单验证:实现输入框单独显示错误提示

解决方案:基于触摸状态的字段级错误控制

核心思路是通过跟踪字段的触摸状态(touched),结合仅验证当前输入字段的逻辑,实现“输入时只显示当前字段错误,已触摸过的字段错误保留显示”的需求,同时不破坏你的FormContext复用设计。

1. 改造Form组件的Context状态

在Form组件中新增touched状态,用于记录哪些字段被用户交互过;同时调整错误更新逻辑,只覆盖当前输入字段的错误,保留其他已触摸字段的错误。

import { createContext, useContext, useState } from 'react';

const FormContext = createContext();

export function Form({ children, validate, onSubmit }) {
  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({}); // 新增:记录字段是否被触摸

  const handleFormChange = (fieldName, value) => {
    // 1. 更新字段值
    setValues(prev => ({ ...prev, [fieldName]: value }));
    // 2. 标记当前字段为已触摸
    setTouched(prev => ({ ...prev, [fieldName]: true }));
    // 3. 仅验证当前字段,更新错误(保留其他已存在的错误)
    const fieldError = validate(fieldName, value, values);
    setErrors(prev => ({
      ...prev,
      [fieldName]: fieldError || null // 无错误则设为null
    }));
  };

  const handleFormSubmit = (e) => {
    e.preventDefault();
    // 提交时全量验证所有字段,并标记所有为已触摸
    const allErrors = {};
    Object.keys(values).forEach(field => {
      allErrors[field] = validate(field, values[field], values);
    });
    setErrors(allErrors);
    setTouched(Object.fromEntries(Object.keys(values).map(key => [key, true])));
    
    // 无错误时执行提交
    if (Object.values(allErrors).every(err => !err)) {
      onSubmit(values);
    }
  };

  return (
    <FormContext.Provider value={{ values, errors, touched, handleFormChange }}>
      <form onSubmit={handleFormSubmit}>{children}</form>
    </FormContext.Provider>
  );
}

2. 改造FormInput组件,基于触摸状态显示错误

在FormInput中,只有当字段已被触摸且存在错误时,才渲染错误提示。

export function FormInput({ label, name, type = 'text' }) {
  const { values, errors, touched, handleFormChange } = useContext(FormContext);

  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        id={name}
        type={type}
        value={values[name] || ''}
        onChange={(e) => handleFormChange(name, e.target.value)}
      />
      {/* 仅当字段已触摸且有错误时显示 */}
      {touched[name] && errors[name] && (
        <span className="error">{errors[name]}</span>
      )}
    </div>
  );
}

3. 父组件(App)的验证规则保持原有结构

validate函数接收当前字段名、字段值和整个表单值,仅返回当前字段的错误信息即可:

import { Form, FormInput } from './FormComponents';

function App() {
  const validate = (fieldName, value, allValues) => {
    switch (fieldName) {
      case 'email':
        if (!value) return '邮箱不能为空';
        if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return '请输入有效的邮箱地址';
        return null;
      case 'password':
        if (!value) return '密码不能为空';
        if (value.length < 6) return '密码长度不能少于6位';
        return null;
      case 'confirmPassword':
        if (!value) return '请确认密码';
        if (value !== allValues.password) return '两次输入的密码不一致';
        return null;
      default:
        return null;
    }
  };

  const handleSubmit = (values) => {
    console.log('表单提交:', values);
    // 执行提交逻辑
  };

  return (
    <div className="app">
      <Form validate={validate} onSubmit={handleSubmit}>
        <FormInput label="邮箱" name="email" />
        <FormInput label="密码" name="password" type="password" />
        <FormInput label="确认密码" name="confirmPassword" type="password" />
        <button type="submit">提交</button>
      </Form>
    </div>
  );
}

export default App;

关键逻辑说明

  • 触摸状态(touched):确保只有用户操作过的字段才会显示错误,避免初始加载或未操作字段显示错误。
  • 局部验证:输入时仅验证当前字段,错误更新时合并原有错误(不清除其他已触摸字段的错误),解决了“输入任意字段显示所有错误”的问题。
  • 提交全量验证:提交时验证所有字段并标记为已触摸,确保用户能看到所有未修正的错误。

这样既保留了你原有的FormContext复用设计,又完美实现了需求:输入时只显示当前字段的错误,已触摸过的字段错误会持续显示直到修正,提交时显示所有错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 12:36:32