Skip to content

fix: 8 处模态框确认按钮缺少 type="button",点确认会触发隐式提交、整页刷新并中断请求 #55

Description

@LittlePenhu

确认弹窗里的按钮写在 <form> 里,但没写 type,而 HTML 里 <button> 的默认 type 恰好就是 submit。这些 <form> 又都没有 action、没有 onSubmit,所以点一下就会走隐式提交:浏览器 GET 当前 URL、整页刷新,onClick 里刚发出去的 fetch 跟着页面一起没了。

用户看到的是"点了确定,页面闪一下,东西还在(或者根本说不清)"。

在真浏览器里跑过一遍之后,结论比我原先写的更麻烦,这里先把它订正掉:请求其实是完整发出去的,服务端收到了,只是响应回来的时候页面已经导航走、客户端拿不到。也就是说这类操作很可能确实生效了,而界面上什么都不变——不是"看着成功其实没执行",是反过来的"看着没执行其实执行了"。下面有实测数据。

涉及的 8 处(main @ 90ed95b):

  • src/pages/AnnouncementsPage.tsx:288(form)/ :291 删除公告
  • src/pages/ProfilePage.tsx:685 / :688 移除 Passkey
  • src/pages/ProfilePage.tsx:703 / :706 解绑 OAuth
  • src/pages/CardsPage.tsx:344 / :347 解绑卡片
  • src/pages/CardsPage.tsx:406 / :409 删除 Access Code 别名
  • src/pages/KeychipPage.tsx:535 / :538 移除机台
  • src/pages/KeychipPage.tsx:570 / :573 取消信任机台
  • src/features/ongeki/OngekiRivalPage.tsx:204 / :207 删除对手

复现方式:找个页面打开对应弹窗点"确定",地址栏会闪一次带 query 的整页加载。

实测(真实浏览器,Edge 153 + Playwright 1.62):本地起构建产物 + 一个模拟后端,点的是 /ongeki/rival 页"移除对手"那个按钮(:204/:207)。两个阶段只有 type 这一个变量不同:

A) <button class="btn btn-danger btn-sm">确定</button>
B) <button class="btn btn-danger btn-sm" type="button">确定</button>
额外整页加载 服务端收到 DELETE 页面侧结果
A 现状(无 type) 1 次 收到了 274ms 时被中断:Failed to fetch
B 只加 type="button" 0 次 收到了 1217ms 后正常 HTTP 200

A 阶段的几个细节:

  • 点击后地址栏变成 /ongeki/rival?(原生表单 GET 留下的 query 串),主框架导航、整页重新加载;
  • 服务端收到了那个 DELETE,但日志显示客户端在响应写出之前就断开了(clientGone: true)——服务端本来要隔 1200ms 才回,而页面侧的 fetch 在 274ms 就报错了,时间点正好是导航发生的时候;
  • 结果是没有成功提示、列表里的对手还在、页面看起来什么都没发生。

B 阶段同一套操作:没有导航,请求 1217ms 后正常拿到 200,对手从列表里消失。

(早前用 jsdom 做的对照——无 type 点击触发 1 次 submit、type="button" 触发 0 次——结论与这里一致,只是它观察不到"请求已经发出去"这一层。如果你们想把这两组数据做成回归用例,我可以把脚本整理成 Playwright spec 提过来。)

顺带说下跟 #25 的关系。KeychipPage.tsx:535 那个 <form> / :538 的按钮,就是 #25 里"删除机台"那一个,但根因不一样:#25 是服务端返回 500(你们当时的回复是"下次服务器更新一并修"),这里是客户端 DOM 行为。而且同样的问题在另外 7 处也有,其中 6 处跟机台无关(公告 / Passkey / OAuth / 解绑卡片 / 卡片别名 / 对手)——也就是说这不是机台专有的毛病。当初那个 ISE 的场景里会不会也叠了这条,我不确定,可能值得一起看看。

修法就是补 type="button":

- <button className="btn btn-danger btn-sm" onClick={...}>
+ <button type="button" className="btn btn-danger btn-sm" onClick={...}>

没直接开 PR,是因为这是 8 个文件、8 处同类改动,想先问一句你们希望一次改完还是自己来。

补充一点:同代码库里 ProfilePage.tsx:673、CardsPage.tsx:229、KeychipPage.tsx:686 都规规矩矩写了 type="button",所以这应该是漏了而不是有意。真想根治的话,把这类"没有输入框 + 确定/取消"的弹窗抽成一个 <ConfirmModal onConfirm>,这个模式就不会再出现。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions