送信中の制御

出品ボタンは2回押される

出品ボタンを押しても、通信が終わるまで画面は何も変わりません。使う人は「押せていないのかな」と思ってもう一度押します。押した回数だけ POST が飛び、同じ商品が2件出品されます。

これは使う人の不注意ではありません。押した手応えを返していない画面のほうが悪いです。直すところは2つあります。

送信中を state で持つ

いま送っている最中かどうかを、旗として持ちます。

const [pending, setPending] = useState(false);

送り始めに true にして、終わったら false に戻します。戻すのは finally の中にします。

setPending(true); try { await fetch("/api/items", { method: "POST", body: JSON.stringify(sent) }); } finally { setPending(false); }

finally は、成功しても失敗しても必ず通ります。ここに戻す処理を置かないと、1回失敗しただけでボタンが二度と押せなくなります。

見た目を止める

<button className="submit" type="submit" disabled={pending}> {pending ? "送信中" : "出品する"} </button>

ボタンが灰色になり、文字が変わります。これで押した手応えが伝わります。

見た目だけでは足りない

ここが今回いちばん大事なところです。フォームは、ボタンを押す以外の道でも送信されます。入力欄で Enter を押しても飛びますし、送信の合図をコードから直接出すこともできます。その道はボタンの disabled を通りません。

だから、処理の入口でも止めます。

async function handleSubmit(e: FormEvent<HTMLFormElement>) { e.preventDefault(); if (pending) return; setPending(true); // ... }

見た目で伝えることと、処理で止めることは役割が違います。両方書いてはじめて二重送信が防げます。

演習

出品フォームに送信中の制御を足し、続けて送信しても1件しか増えないようにします。

ヒント

App.tsx
mock-api.json
App.tsx
mock-api.json
プレビュー

できているか

  • はじめの5件が出ている
  • 件数が出ている
  • 続けて送信しても1件しか増えない
  • 送信が終わればまた出品できる