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

React表单需提交两次才生效?useState与Express API交互问题

问题解决:React表单首次提交使用默认值的问题

问题分析

你遇到的核心问题是React状态更新的异步特性+闭包捕获旧状态导致的:

  • 输入内容后立即提交时,onChange中的setUser是异步执行的,此时getUsers函数捕获的还是更新前的user默认值
  • 第二次提交时,组件已完成状态更新的渲染,getUsers捕获到最新的user值,因此能正常提交

另外代码中存在潜在问题:拼接的fetchUrl末尾可能多出一个&,可能导致后端API解析参数出错。

解决方案

方案1:提交时直接从表单获取值(最可靠)

不依赖user状态,提交时直接从表单元素提取最新值,彻底规避异步状态的影响:

const getUsers = (event) => {
  event.preventDefault();
  // 从表单获取所有输入值
  const formData = new FormData(event.target);
  const formValues = Object.fromEntries(formData);
  
  let fetchUrl = "http://localhost:1234/users?";
  // 拼接参数并处理数值类型
  if (formValues.fav_color) fetchUrl += `fav_color=${formValues.fav_color}&`;
  if (formValues.dist) fetchUrl += `dist=${formValues.dist}&`;
  if (formValues.origin) fetchUrl += `origin=${formValues.origin}&`;
  if (formValues.min_age) fetchUrl += `min_age=${parseInt(formValues.min_age)}&`;
  if (formValues.max_age) fetchUrl += `max_age=${parseInt(formValues.max_age)}&`;
  
  // 移除末尾多余的&(如果存在)
  fetchUrl = fetchUrl.replace(/&$/, "");

  fetch(fetchUrl)
    .then((response) => response.json())
    .then((data) => setUsers(data))
    .catch(console.error);
};

方案2:使用函数式更新确保状态正确

修改handleChange,使用React函数式更新语法,保证每次状态更新都基于最新的前序状态,避免闭包问题:

const handleChange = (event) => {
  const { name, value } = event.target;
  // 基于最新的prevUser状态更新
  setUser(prevUser => ({
    ...prevUser,
    [name]: value
  }));
};

同时需按照方案1的方式修复fetchUrl末尾的多余&问题。

完整修正后的代码

import { useState, useEffect } from "react";

const USER_DEFAULT = {
  fav_color: "",
  dist: "",
  origin: "",
  min_age: "",
  max_age: "",
};

function UserForm() {
  const [user, setUser] = useState(USER_DEFAULT);
  const [users, setUsers] = useState([]);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(`http://localhost:1234/users/`);
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        console.error(err);
      }
    }
    fetchData();
  }, []);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setUser(prevUser => ({
      ...prevUser,
      [name]: value
    }));
  };

  const getUsers = (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);
    const formValues = Object.fromEntries(formData);
    
    let fetchUrl = "http://localhost:1234/users?";
    if (formValues.fav_color) fetchUrl += `fav_color=${formValues.fav_color}&`;
    if (formValues.dist) fetchUrl += `dist=${formValues.dist}&`;
    if (formValues.origin) fetchUrl += `origin=${formValues.origin}&`;
    if (formValues.min_age) fetchUrl += `min_age=${parseInt(formValues.min_age)}&`;
    if (formValues.max_age) fetchUrl += `max_age=${parseInt(formValues.max_age)}&`;
    
    // 移除末尾多余的&
    fetchUrl = fetchUrl.replace(/&$/, "");

    fetch(fetchUrl)
      .then((response) => response.json())
      .then((data) => setUsers(data))
      .catch(console.error);
  };

  return (
    <div className="container text-center">
      <div className="row">
        <div className="col-3 pr-5">
          <form onSubmit={getUsers}>
            <h4 className="mb-4">Find Users!</h4>

            <div className="form-group">
              <label htmlFor="fav_color">Favorite Color:</label>
              <input
                id="fav_color"
                name="fav_color"
                type="text"
                className="form-control"
                value={user.fav_color}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="dist">Distance:</label>
              <input
                id="dist"
                name="dist"
                type="number"
                className="form-control"
                value={user.dist}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="origin">Origin (Long, Lat):</label>
              <input
                id="origin"
                name="origin"
                type="text"
                className="form-control"
                value={user.origin}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="min_age">Minimum Age:</label>
              <input
                id="min_age"
                name="min_age"
                type="number"
                className="form-control"
                value={user.min_age}
                onChange={handleChange}
              />
            </div>
            <div className="form-group">
              <label htmlFor="max_age">Maximum Age:</label>
              <input
                id="max_age"
                name="max_age"
                type="number"
                className="form-control"
                value={user.max_age}
                onChange={handleChange}
              />
            </div>
            <div className="mt-4">
              <button className="btn btn-success mr-2" type="submit">
                <i className="bi bi-file-earmark-check"></i> Search
              </button>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
}

export default UserForm;

额外说明

  • 方案1直接从表单取数,完全绕开状态异步问题,是表单提交场景下的最优解
  • 方案2优化了状态更新的可靠性,但用户快速提交时仍可能出现问题
  • 代码中还优化了useEffect的异步请求写法,用try/catch替代链式catch,更符合现代异步代码规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 09:17:04