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

React中使用useState实现表单验证及提交按钮状态控制问题

问题描述

我用MUI的TextField和提交按钮实现了一个表单,用于收集brandName、supplierName和expiryDate三个字段,需求是:存在空字段时禁用提交按钮,全部填完后启用按钮。

现有状态声明代码

const[brandName, setBrandName] = useState("");
const[supplierName, setSupplierName] = useState("");
const[expiryDate, setExpiryDate] = useState(null);
const[brandNameError, setBrandNameError] = useState(false);
const[supplierNameError, setSupplierNameError] = useState(false);
const[expiryDateError, setExpiryDateError] = useState(false);

const[submitButton, setSubmitButton] = useState(true);

验证函数代码

// 校验品牌名称错误
const brandNameValidateOnBlur = ()=>{
  if(brandName === ""){
    setBrandNameError(true);
  }
}

// 校验供应商名称错误
const supplierNameValidateOnBlur = ()=>{
  if(supplierName === ""){
      setSupplierNameError(true);
  }
}
// 校验过期日期错误
const expiryDateValidateOnBlur = ()=>{
  if(expiryDate === ""){
      setExpiryDateError(true);
  }
}

// 校验所有输入,决定是否启用提交按钮
const checkAllInputs = ()=>{
    if(brandName !== "" && supplierName !== "" && expiryDate !== ""){
        setSubmitButton(false);
    }else{
        setSubmitButton(true);
    }
}

表单组件代码

<TextField  fullWidth id="productName" label="Product Name" 
  value={brandName.toLowerCase()} variant="outlined" 
  onChange={(data)=>{setBrandName(data.target.value.toUpperCase());checkAllInputs()}}
  onBlur={brandNameValidateOnBlur}
  onFocus={()=>setBrandNameError(false)}
  error={brandNameError}
  helperText = {brandNameError ? "Enter Brand Name" : ""}
/>
<TextField id="supplierName"  
  label="Supplier Name" 
  value={supplierName.toLowerCase()} variant="outlined"
  onBlur={supplierNameValidateOnBlur}
  onFocus={()=>setSupplierNameError(false)}
  error={supplierNameError}
  helperText={supplierNameError ? "Enter Supplier Name " : ""}
  onChange={(data)=>{setSupplierName(data.target.value.toUpperCase());checkAllInputs()}} />
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DesktopDatePicker
    label="Date Expiry"
    inputFormat="MM/DD/YYYY"
    value={expiryDate}
    onBlur={expiryDateValidateOnBlur}
    onFocus={()=>setExpiryDateError(false)}
    error={expiryDateError}
    helperText={expiryDateError ? "Enter Expiry Date ": ""}
    onChange={(selectedDate)=>
    {setExpiryDate(selectedDate.format("MM/DD/YYYY"));checkAllInputs()}}
    renderInput={(params) => <TextField {...params} />}
  />
</LocalizationProvider>
<Button type='button' variant='contained' id="submitButton" disabled={submitButton}
style={{backgroundColor:'orangered'}} onClick={addData}>Add Data</Button>

遇到的问题

输入第一个字符时,useState已更新,但checkAllInputs函数无法获取最新状态,导致提交按钮状态未正确切换,输入第二个字符时才恢复正常。

尝试过的方案

  1. 使用useEffect监听状态变化触发校验:
useEffect(()=>{
  checkAllInputs();
},[brandName, supplierName, expiryDate]);

该方案有效,但担心带来不必要的性能开销。

  1. 直接操作DOM获取输入值:
const checkAllInputsWithDom = ()=>{
 let brandNameEntry = document.getElementById("brandName").value;
 let supplierNameEntry = document.getElementById("supplierName").value;
 let expiryDateEntry = document.getElementById("expiryDate").value;
 if(brandNameEntry !=="" && supplierNameEntry !== ""  && expiryDateEntry !== ""){
    setSubmitButton(false);
 }else{
    setSubmitButton(true);
 }
}

可直接生效,但违反React状态驱动原则,且无法正确处理日期字段的错误提示。

提问

有没有不使用useEffect的合规实现方式?


解决方案

完全可以不依赖useEffect,核心思路是取消单独维护submitButton状态,直接通过计算属性实时判断按钮是否禁用,同时优化校验逻辑避免依赖状态更新延迟的问题。

优化步骤:

  1. 移除submitButton状态,在按钮的disabled属性中直接计算禁用状态:
<Button 
  type='button' 
  variant='contained' 
  id="submitButton" 
  disabled={!isFormValid()}
  style={{backgroundColor:'orangered'}} 
  onClick={addData}
>
  Add Data
</Button>
  1. 新增表单有效性判断函数,基于当前状态实时计算:
const isFormValid = () => {
  // 加入trim处理避免全空格的无效输入,日期字段初始为null,需判断非空
  return brandName.trim() !== "" && supplierName.trim() !== "" && expiryDate !== null;
};
  1. 优化失焦校验函数,简化逻辑并修正日期字段的判断错误:
const brandNameValidateOnBlur = ()=>{
  setBrandNameError(brandName.trim() === "");
}

const supplierNameValidateOnBlur = ()=>{
  setSupplierNameError(supplierName.trim() === "");
}

const expiryDateValidateOnBlur = ()=>{
  // 日期初始状态是null,这里修正判断逻辑
  setExpiryDateError(expiryDate === null);
}
  1. 修改onChange事件,无需再调用checkAllInputs:
    品牌名称的onChange:
onChange={(data)=>{
  setBrandName(data.target.value.toUpperCase());
}}

日期选择器的onChange:

onChange={(selectedDate)=>{
  setExpiryDate(selectedDate.format("MM/DD/YYYY"));
}}

方案优势

  • 完全遵循React状态驱动原则,无DOM操作,保留了失焦错误提示逻辑。
  • 组件每次状态更新都会重新渲染,按钮的禁用状态会通过isFormValid()实时计算,无需额外维护状态或监听副作用。
  • 减少了状态更新次数,性能比useEffect方案更高效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 22:45:45