React 第五十六节 Router 中useSubmit的使用详解及注意事项

news2025/7/23 5:25:28

前言

useSubmitReact Router v6.4+ 引入的强大钩子,用于以编程方式提交表单数据。
它提供了对表单提交过程的精细控制,特别适合需要自定义提交行为或非标准表单场景的应用。

一、useSubmit 核心用途

  1. 编程式表单提交:不依赖 <form> 元素手动提交数据
  2. 自定义提交逻辑:在提交前/后执行额外操作
  3. 动态表单构建:根据应用状态生成提交数据
  4. 替代传统表单:在复杂UI中实现表单功能

二、useSubmit 基本用法

import { useSubmit } from 'react-router-dom';

function CustomSubmitButton() {
  const submit = useSubmit();
  
  const handleSubmit = () => {
    // 构建表单数据
    const formData = new FormData();
    formData.append('username', 'john_doe');
    formData.append('password', 'secure123');
    
    // 提交数据
    submit(formData, {
      method: 'post',
      action: '/login',
      encType: 'application/x-www-form-urlencoded'
    });
  };
  
  return (
    <button onClick={handleSubmit}>登录</button>
  );
}

三、useSubmit 参数详解

3.1、submit 函数参数

```javascript
submit(
    data: FormData | URLSearchParams | { [key: string]: string } | null,
    options?: {
        method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
        action?: string;
        encType?: 'application/x-www-form-urlencoded' | 'multipart/form-data';
        replace?: boolean;
    }
)
```

3.2、选项说明:

method:HTTP方法(默认为"GET")
action:提交的目标URL(默认为当前URL
encType:表单编码类型(默认为"application/x-www-form-urlencoded")
replace:是否替换历史记录而不是添加新条目(默认为false

四、useSubmit 实际应用场景

4.1、自定义删除按钮

import { useSubmit } from 'react-router-dom';

function DeleteButton({ itemId }) {
  const submit = useSubmit();
  
  const handleDelete = () => {
    if (window.confirm('确定要删除此项吗?')) {
      submit(
        { id: itemId }, 
        { 
          method: 'delete', 
          action: `/items/${itemId}` 
        }
      );
    }
  };
  
  return (
    <button 
      onClick={handleDelete}
      className="delete-btn"
      aria-label="删除项目"
    >
      🗑️ 删除
    </button>
  );
}

4.2、动态搜索表单

import { useState, useEffect } from 'react';
import { useSubmit, useLocation } from 'react-router-dom';

function SearchBar() {
  const submit = useSubmit();
  const location = useLocation();
  const [query, setQuery] = useState('');
  
  // 使用防抖自动提交搜索
  useEffect(() => {
    const timerId = setTimeout(() => {
      if (query.trim() !== '') {
        const formData = new FormData();
        formData.append('q', query);
        
        submit(formData, {
          method: 'get',
          action: '/search',
          replace: query === '' // 空查询时替换历史记录
        });
      }
    }, 300);
    
    return () => clearTimeout(timerId);
  }, [query, submit]);
  
  // 初始化时从URL读取查询参数
  useEffect(() => {
    const params = new URLSearchParams(location.search);
    setQuery(params.get('q') || '');
  }, [location.search]);
  
  return (
    <div className="search-container">
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
        className="search-input"
      />
      {query && (
        <button 
          onClick={() => setQuery('')}
          className="clear-btn"
        ></button>
      )}
    </div>
  );
}

4.3、多步骤表单

import { useState } from 'react';
import { useSubmit } from 'react-router-dom';

function MultiStepForm() {
  const submit = useSubmit();
  const [step, setStep] = useState(1);
  const [formData, setFormData] = useState({
    personal: {},
    address: {},
    preferences: {}
  });
  
  const handleNextStep = () => {
    if (step < 3) {
      setStep(step + 1);
    } else {
      // 最终提交
      submitForm();
    }
  };
  
  const handlePrevStep = () => {
    setStep(step - 1);
  };
  
  const updateFormData = (section, data) => {
    setFormData(prev => ({
      ...prev,
      [section]: { ...prev[section], ...data }
    }));
  };
  
  const submitForm = () => {
    // 构建完整表单数据
    const finalData = {
      ...formData.personal,
      ...formData.address,
      ...formData.preferences
    };
    
    submit(finalData, {
      method: 'post',
      action: '/register',
      encType: 'application/json'
    });
  };
  
  return (
    <div className="multi-step-form">
      <div className="progress-bar">
        <div className={`step ${step >= 1 ? 'active' : ''}`}>个人信息</div>
        <div className={`step ${step >= 2 ? 'active' : ''}`}>地址信息</div>
        <div className={`step ${step >= 3 ? 'active' : ''}`}>偏好设置</div>
      </div>
      
      <div className="form-content">
        {step === 1 && (
          <PersonalInfo 
            data={formData.personal}
            onChange={data => updateFormData('personal', data)}
          />
        )}
        {step === 2 && (
          <AddressInfo 
            data={formData.address}
            onChange={data => updateFormData('address', data)}
          />
        )}
        {step === 3 && (
          <Preferences 
            data={formData.preferences}
            onChange={data => updateFormData('preferences', data)}
          />
        )}
      </div>
      
      <div className="form-navigation">
        {step > 1 && (
          <button onClick={handlePrevStep}>上一步</button>
        )}
        <button onClick={handleNextStep}>
          {step < 3 ? '下一步' : '提交注册'}
        </button>
      </div>
    </div>
  );
}

4.4、文件上传

import { useRef, useState } from 'react';
import { useSubmit } from 'react-router-dom';

function FileUploader() {
  const submit = useSubmit();
  const fileInputRef = useRef(null);
  const [isUploading, setIsUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  
  const handleFileChange = async (e) => {
    const file = e.target.files[0];
    if (!file) return;
    
    setIsUploading(true);
    
    try {
      // 创建FormData对象
      const formData = new FormData();
      formData.append('file', file);
      formData.append('description', '用户上传的文件');
      
      // 创建XMLHttpRequest以获取上传进度
      const xhr = new XMLHttpRequest();
      
      // 进度事件处理
      xhr.upload.addEventListener('progress', (event) => {
        if (event.lengthComputable) {
          const percent = Math.round((event.loaded / event.total) * 100);
          setProgress(percent);
        }
      });
      
      // 完成事件处理
      xhr.addEventListener('load', () => {
        setIsUploading(false);
        setProgress(0);
        alert('文件上传成功!');
      });
      
      // 错误处理
      xhr.addEventListener('error', () => {
        setIsUploading(false);
        alert('文件上传失败');
      });
      
      // 打开并发送请求
      xhr.open('POST', '/upload');
      xhr.send(formData);
      
      // 或者使用submit钩子(但无法获取进度)
      // submit(formData, {
      //   method: 'post',
      //   action: '/upload',
      //   encType: 'multipart/form-data'
      // });
      
    } catch (error) {
      setIsUploading(false);
      console.error('上传失败:', error);
    }
  };
  
  return (
    <div className="file-uploader">
      <input 
        type="file" 
        ref={fileInputRef} 
        onChange={handleFileChange}
        style={{ display: 'none' }} 
      />
      
      <button 
        onClick={() => fileInputRef.current.click()}
        disabled={isUploading}
      >
        选择文件
      </button>
      
      {isUploading && (
        <div className="upload-progress">
          <div 
            className="progress-bar" 
            style={{ width: `${progress}%` }}
          ></div>
          <span>{progress}%</span>
        </div>
      )}
    </div>
  );
}

4.5、乐观更新与表单提交

import { useState } from 'react';
import { useSubmit } from 'react-router-dom';

function TodoItem({ todo }) {
  const submit = useSubmit();
  const [isCompleted, setIsCompleted] = useState(todo.completed);
  const [isUpdating, setIsUpdating] = useState(false);
  
  const handleToggle = async () => {
    // 乐观更新:立即更新UI
    const newCompleted = !isCompleted;
    setIsCompleted(newCompleted);
    setIsUpdating(true);
    
    try {
      // 准备表单数据
      const formData = new FormData();
      formData.append('id', todo.id);
      formData.append('completed', newCompleted.toString());
      
      // 提交更新
      submit(formData, {
        method: 'patch',
        action: `/todos/${todo.id}`,
        encType: 'application/x-www-form-urlencoded'
      });
      
    } catch (error) {
      // 出错时回滚状态
      setIsCompleted(!newCompleted);
      console.error('更新失败:', error);
    } finally {
      setIsUpdating(false);
    }
  };
  
  return (
    <li className={`todo-item ${isCompleted ? 'completed' : ''}`}>
      <input
        type="checkbox"
        checked={isCompleted}
        onChange={handleToggle}
        disabled={isUpdating}
      />
      <span className="todo-text">{todo.text}</span>
      {isUpdating && <span className="updating-indicator">更新中...</span>}
    </li>
  );
}

五、useSubmit 与相关API对比

在这里插入图片描述

六、useSubmit 最佳实践

  1. 数据验证:提交前验证数据

  2. 用户反馈:提交时显示加载状态

  3. 错误处理:捕获并处理提交错误

  4. 编码类型:

    a.简单数据:application/x-www-form-urlencoded

    b.文件上传:multipart/form-data

    c.JSON数据:手动序列化并设置适当headers

  5. 性能优化:大文件上传使用分块或流式上传

// 带验证的提交示例
function ValidatedForm() {
  const submit = useSubmit();
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');
  
  const handleSubmit = () => {
    // 验证邮箱格式
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      setError('请输入有效的邮箱地址');
      return;
    }
    
    // 提交数据
    submit({ email }, { method: 'post', action: '/subscribe' });
  };
  
  return (
    <div className="subscribe-form">
      <input
        type="email"
        value={email}
        onChange={(e) => {
          setEmail(e.target.value);
          setError('');
        }}
        placeholder="输入您的邮箱"
        className={error ? 'error' : ''}
      />
      {error && <div className="error-message">{error}</div>}
      <button onClick={handleSubmit}>订阅</button>
    </div>
  );
}

七、useSubmit 注意事项

  1. 路由上下文:必须在路由组件中使用(在 <Router> 上下文中)

  2. 数据格式:

    对象会被转换为 URLSearchParams

    文件上传必须使用 FormData

  3. GET请求:数据会作为查询参数添加到URL

  4. 重定向:提交后服务器应返回重定向响应

  5. 状态管理:提交不会自动管理加载状态,需自行实现

总结

useSubmitReact Router 中处理表单提交的高级工具,特别适合以下场景:

  1. 自定义表单逻辑:当需要超出标准表单的行为时
  2. 动态数据提交:根据应用状态生成提交数据
  3. 非表单元素提交:从按钮或其他UI元素触发提交
  4. 复杂交互流程:如多步骤表单、乐观更新
  5. 文件上传:处理二进制数据提交

通过合理使用 useSubmit,您可以构建更灵活、更强大的表单交互体验,同时保持与 React Router 数据路由模型的无缝集成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2402641.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为云学堂-云原生开发者认证课程列表

华为云学堂-云原生认证 云原生开发者认证的前5个课程

理解网络协议

1.查看网络配置 : ipconfig 2. ip地址 : ipv4(4字节, 32bit), ipv6, 用来标识主机的网络地址 3.端口号(0~65535) : 用来标识主机上的某个进程, 1 ~ 1024 知名端口号, 如果是服务端的话需要提供一个特定的端口号, 客户端的话是随机分配一个端口号 4.协议 : 简单来说就是接收数据…

全球知名具身智能/AI机器人实验室介绍之AI FACTORY基于慕尼黑工业大学

全球知名具身智能/AI机器人实验室介绍之AI FACTORY基于慕尼黑工业大学 TUM AI FACTORY&#xff0c;即KI.FABRIK&#xff0c;是德国慕尼黑工业大学&#xff08;TUM&#xff09;在巴伐利亚州推出的一个旗舰项目&#xff0c;旨在打造未来工厂&#xff0c;将传统工厂转变为由人工智…

DASCTF

[DASCTF X 0psu3十一月挑战赛&#xff5c;越艰巨越狂热]EzPenetration Tip:数据库里的邮箱key已更改为管理员密码&#xff0c;拿到后可直接登录 打开靶机&#xff0c;用Wappalyzer分析网站&#xff0c;可以看到管理系统是Wordpress&#xff0c;因此可以尝试用WPSSCAN扫描公开…

ModBus总线协议

一、知识点 1. 什么是Modbus协议&#xff1f; Modbus 是一种工业通信协议&#xff0c;最早由 Modicon 公司在1979年提出&#xff0c;目的是用于 PLC&#xff08;可编程逻辑控制器&#xff09;之间的数据通信。它是主从式通信&#xff0c;即一个主机&#xff08;主设备&#xf…

【计算机网络】非阻塞IO——poll实现多路转接

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a;【计算机网络】非阻塞IO——select实现多路转接 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、…

DAX权威指南8:DAX引擎与存储优化

文章目录 十七、DAX引擎17.1 DAX 引擎的体系结构17.1.1 表格模型的双引擎架构17.1.2 存储引擎的三种模式17.1.2.1 VertiPaq引擎17.1.2.2 DirectQuery 引擎17.1.2.3 对比与最佳实践 17.1.3 数据刷新 17.2 理解 VertiPaq 存储引擎17.2.1 列式数据库17.2.2 VertiPaq 压缩17.2.2.1 …

智慧货运飞船多维度可视化管控系统

图扑搭建智慧货运飞船可视化系统&#xff0c;借数字孪生技术&#xff0c;高精度复刻货运飞船外观、结构与运行场景。整合多维度数据&#xff0c;实时呈现飞行状态、设备参数等信息&#xff0c;助力直观洞察货运飞船运行逻辑&#xff0c;为航天运维、任务推演及决策提供数字化支…

电脑开不了机,主板显示67码解决过程

文章目录 现象分析内存条问题BIOS设置问题其它问题 解决清理内存条金手指所需工具操作步骤注意事项 电脑在运行过程中&#xff0c;显示内存不足&#xff0c;重启电脑却无法启动。 现象 System Initialization 主板风扇是转的&#xff0c;也有灯光显示&#xff0c;插上屏幕&am…

自托管图书搜索引擎Bookologia

简介 什么是 Bookologia &#xff1f; Bookologia 是一个专门的书籍搜索引擎&#xff0c;可以在几秒钟内找到任何书籍。它是开源的&#xff0c;可以轻松自托管在 Docker 上&#xff0c;为用户提供一个简单而高效的书籍查找体验。 主要特点 简洁的用户界面&#xff1a;界面设计…

前端flex、grid布局

flex布局 弹性布局是指通过调整其内元素的宽高&#xff0c;从而在任何的显示设备上实现对可用显示空间最佳填充的能力。弹性容器扩展其内元素来填充可用空间&#xff0c;或将其收缩来避免溢出 简单来说&#xff0c;弹性盒子模型&#xff0c;是为了你的网页可以在不同分辨率设…

Maven相关问题:jna版本与ES冲突 + aop失效

文章目录 1、背景2、解决3、一点思考4、环境升级导致AOP失效5、okhttp Bean找不到6、总结 记录一些Maven依赖相关的思考 1、背景 做一个监控指标收集&#xff0c;用一下jna依赖&#xff1a; <dependency><groupId>net.java.dev.jna</groupId><artifact…

Tomcat全方位监控实施方案指南

#作者&#xff1a;程宏斌 文章目录 一&#xff0e;二进制部署1、安装包信息2、新建配置文件2.1 配置config.yaml文件2.2 上传jar包 3、修改配置3.1 备份3.2 修改bin目录下的startup.sh文件 4、重启tomcat5、访问测试 二&#xff0e;docker部署1、临时方案1.1、重新启动容器1.2…

To be or Not to be, That‘s a Token——论文阅读笔记——Beyond the 80/20 Rule和R2R

本周又在同一方向上刷到两篇文章&#xff0c;可以说&#xff0c;……同学们确实卷啊&#xff0c;要不卷卷开放场域的推理呢&#xff1f; 这两篇都在讲&#xff1a;如何巧妙的利用带有分支能力的token来提高推理性能或效率的。 第一篇叫 Beyond the 80/20 Rule: High-Entropy Mi…

《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P38 变量复制&#xff08;Variable Replication&#xff09;》 的学习笔记&#xff0c;该系列教学视频为计算机工程师、程序员、游戏开发者、作家&#xff08;Engineer, Programmer, Game Developer, Author&#xff09…

AWS API Gateway配置日志

问题 访问API Gateway接口出现了403问题&#xff0c;具体报错如下&#xff1a; {"message":"Missing Authentication Token"}需要配置AWS API Gateway日志&#xff0c;看请求过程是什么样子的。 API Gateway 先找到API Gateway的的日志角色&#xff0c…

Towards Open World Object Detection概述(论文)

论文&#xff1a;https://arxiv.org/abs/2103.02603 代码&#xff1a;https://github.com/JosephKJ/OWOD Towards Open World Object Detection 迈向开放世界目标检测 Abstract 摘要 Humans have a natural instinct to identify unknown object instances in their environ…

轻松备份和恢复 Android 系统 | 4 种解决方案

我们通常会在 Android 手机上存储大量重要的个人数据&#xff0c;包括照片、视频、联系人、信息等等。如果您不想丢失宝贵的数据&#xff0c;可以备份 Android 数据。当您需要访问和使用这些数据时&#xff0c;可以将其恢复到 Android 设备。如果您想了解 Android 备份和恢复&a…

具备强大的数据处理和分析能力的智慧地产开源了

智慧地产视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。 AI是新形势下数…

【iSAQB软件架构】软件架构中构建块的视图:黑箱、灰箱和白箱及其交互机制

在软件架构描述中&#xff0c;黑箱视图&#xff08;Black-box&#xff09;、灰箱视图&#xff08;Gray-box&#xff09;和白箱视图&#xff08;White-box&#xff09; 是不同抽象层级的构建模块表示方式&#xff0c;用于满足不同受众和设计阶段的需求。以下是基于ISAQB标准的清…