無効なボタンに aria-disabled を使ったときの React Hook Form で二重送信防止
に公開背景
form で submit ボタンといえば当たり前のように disabled を使用して二重送信を防いだり、バリデーションにエラーが発生しているときに送信できないようにしていました。
しかし、disabled を使うと Tab キーでフォーカスが当たらないため支援技術を使用しているユーザーがボタンの存在を認識できないという問題があるようです。(参考リンク参照のこと)
React Hook Form で二重送信を防ぐために form.formState.isSubmitting を disabled に指定していたところを aria-disabled に少しだけつまづいたので忘録として残します。
解決策
少しスマートではないですが、formState.isSubmitting が true の場合は onSubmit に重複 submit 防止用の関数を割り当てて、 false の場合は handleSubmit を割り当てました。
const [message, setMessage] = useState("");
const form = useForm({});
// submit の処理
const handleSubmit = form.handleSubmit(async (data)=>{
await api(data);
})
// submit の中断とエラーメッセージ設定
const preventDuplicatedSubmit = (e)=>{
e.preventDefault();
message = setMessage("すでに送信中のため送信処理を実行しませんでした。")
}
return (
<form onSubmit={form.formState.isSubmitting ? preventDuplicatedSubmit : handleSubmit}> //
{/* input fields ... */}
<button aria-disabled={form.formState.isSubmitting}>Submit</button>
<div aria-live="assertive"> // 重複実行時はエラーのメッセージを表示。
{message}
</div>
</form>
)
つまづいた点
最初は handleSubmit 内のイベントハンドラで form.formState.isSubmitting が true のときは早期 return するように実装しました。
しかし当たり前でしたが、 form.handleSubmit() 内で form.formState.isSubmitting は常に true なのでメインの処理が実行できません。
const handleSubmit = form.handleSubmit(async (data)=>{
// 常に true のため毎回早期 return される
if (form.formState.isSubmitting){
return;
}
await api(data); // ここに到達しない
})
続いて isProcessing のような変数を form.handleSubmit 内で true / false 切り替えて、 true の場合は早期 return するように変更しました。
すると早期 return したタイミングで form.handleSubmit.isSubmitting が false になってしまいました。
const [isProcessing, setIsProcessing] = useState(false);
const handleSubmit = form.handleSubmit(async (data)=>{
if (isProcessing) {
return; // ここで return が実行されると form.formState.isSubmitting が false になってしまう
}
setIsProcessing(true);
await api(data);
setIsProcessing(false); // ホントはここまで実行されたあとに form.formState.isSubmitting が false になって欲しい
})
return (
<form onSubmit={form.formState.isSubmitting ? undefined : handleSubmit}>
{/* input fields */}
<button aria-disabled={form.formState.isSubmitting}>Submit</button> {/* aria-disabled だと opacity が設定されて薄くなるようにしているが、2回目クリックすると opacity が外れてしまう */}
</form>
)
ということで解決策に記載の方法に落ち着きました。