送信中の制御
出品ボタンは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件しか増えないようにします。