
社内アプリで同じデータが2件・3件と登録されているというトラブルに遭遇したことはありませんか。原因のほとんどは送信ボタンの連打です。処理が重いときやネットワークが遅いときに、ユーザーが押せた?と思って何度もボタンをタップしてしまい、同じデータが複数作られます。この記事では、変数と DisplayMode を組み合わせた連打防止の実装方法を解説します。
なぜ連打問題が起きるのか
Power Apps で SubmitForm や Patch 関数を実行したとき、処理が完了するまでの数秒間、画面には何も変化が起きません。特にモバイル環境や SharePoint リストへの書き込みは、通信状況によって 1〜3 秒かかることもあります。ユーザーはボタンが反応していないと判断して、もう一度タップします。
私が展開した日報アプリで、まさにこの問題が起きました。20人のスタッフが使うアプリで、1日に数件ずつ重複データが登録されていたのです。現場からは、保存を何回か押さないと保存されない気がして…という声がありました。処理中であることをユーザーに伝えないまま放置したのが原因でした。
解決策は2つのアプローチを組み合わせることです。1つ目は処理中のボタンを押せない状態にすること。2つ目はいま処理中ですよとユーザーに伝えることです。
varIsSubmitting 変数でボタンを制御する
基本的な実装パターンを解説します。varIsSubmitting という変数を用意し、送信処理の前後でフラグを切り替えます。処理中は true になるため、ボタンを DisplayMode.Disabled に変えて押せなくします。
変数の基本的な使い方を理解していれば、実装は難しくありません。UpdateContext か Set のどちらを使うかはスコープによって選びます。フォーム送信は通常1画面内の処理なので UpdateContext で十分ですが、複数画面をまたぐ場合は Set を使います。
送信ボタンの OnSelect
// 送信ボタンの OnSelect
UpdateContext({varIsSubmitting: true});
SubmitForm(Form1);
UpdateContext({varIsSubmitting: false})
送信ボタンの DisplayMode プロパティ
// 送信ボタンの DisplayMode プロパティ
If(varIsSubmitting, DisplayMode.Disabled, DisplayMode.Edit)

ただし、SubmitForm はフォームコントロールの非同期処理を内包しているため、OnSelect の3行目(varIsSubmitting を false に戻す処理)が SubmitForm の完了を待たずに実行されることがあります。確実な制御には OnSuccess・OnFailure イベントでフラグをリセットする方法がより堅牢です。
// フォームの OnSuccess プロパティ
UpdateContext({varIsSubmitting: false});
Navigate(Screen_List, ScreenTransition.Fade)
// フォームの OnFailure プロパティ
UpdateContext({varIsSubmitting: false})
Or 条件で複数バリデーションと組み合わせる
実際のフォームでは、送信ボタンの制御条件が連打防止だけでなく、バリデーションの結果とも連動していることがほとんどです。複数の条件を Or でまとめることで、1つでも問題があればボタンが押せない設計にできます。
// 送信ボタンの DisplayMode(バリデーション複合)
If(
Or(
varIsSubmitting,
IsBlank(TextInput_Name.Text),
IsBlank(TextInput_Email.Text),
lbl_EmailError.Visible
),
DisplayMode.Disabled,
DisplayMode.Edit
)
この Or 条件をそのまま書き続けると、フィールドが増えるたびに条件式が長くなり管理が難しくなります。そこで名前付き計算式の書き方を使い、formValid という named formula にまとめるアプローチが有効です。App.Formulas に定義することで、各ボタンの条件式を formValid の1参照だけに簡潔化できます。
タイマーを使った堅牢な送信パターン
モダンフォームを使っている場合は、タイマーコントロールを組み合わせた設計がより確実です。Power Apps の OnSelect は処理を同時評価するため、変数のセット→バリデーション再評価→フォーム送信という順序を素直な記述で保証できません。
タイマーパターンは次のように設計します。送信ボタンを押すとタイマーが起動し、100〜200ミリ秒後に OnTimerEnd が発火します。この時点でバリデーションが再評価されているため、formValid が true の場合のみ SubmitForm を実行します。
// 送信ボタンの OnSelect
Set(varFormSubmitted, true);
UpdateContext({varIsSubmitting: true});
UpdateContext({varStartTimer: false});
UpdateContext({varStartTimer: true}) // タイマー起動
// Timer コントロールの Duration: 150
// Timer コントロールの Start プロパティ: varStartTimer
// Timer コントロールの OnTimerEnd
If(
formValid,
SubmitForm(Form1),
UpdateContext({varIsSubmitting: false})
)

このパターンは少し複雑に見えますが、一度テンプレートとして作ってしまえばどのフォームにも使い回せます。私はこのパターンをスニペットとして保存しており、新しいフォームを作るたびにコピーして使っています。
ローディングスピナーでユーザーに処理中を伝える
連打を防ぐだけでなく、ユーザーにいま処理中ですと伝えることも重要です。ボタンが灰色になるだけでは気づかないユーザーもいます。簡単な方法は varIsSubmitting が true のときにボタン上にラベルを重ねて、保存中...と表示することです。
// ローディングラベルの Visible プロパティ
varIsSubmitting
// ローディングラベルの Text プロパティ
"保存中..."
もう少し手間をかける場合は、GIF アニメーション画像をボタンの上に重ねて表示する方法もあります。ローディングスピナーのGIFをアプリのメディアに追加し、Image コントロールの Visible を varIsSubmitting で制御します。
視覚フィードバックの設計は、バリデーションのエラー表示と同様にユーザーが今何が起きているかわかる状態を作ることが目的です。コンテナを使ったレイアウト設計と組み合わせることで、ローディング表示の重ね配置もすっきり管理できます。
まとめ:処理中を見える化する設計で信頼感を上げる
連打防止の実装は varIsSubmitting 変数と DisplayMode.Disabled の組み合わせで実現できます。さらにローディング表示を加えることで、ユーザーが迷わずアプリを使える状態になります。
重複登録が発生するアプリは、現場への信頼感を大きく損ないます。またデータが重複してたという経験を積み重ねると、アプリへの信頼が失われ、最終的には紙に戻ってしまうことすらあります。連打防止はシンプルな実装ですが、アプリの信頼性を守る重要な設計です。地道なUX改善の積み重ねが、長く使われるアプリを作ります。
入力エラーに関する一覧はこちらの記事をご覧ください。