本文へスキップ
NextCode.ltd
開発記録

日本語の「変換確定のEnter」で送信される事故を、3つの層で止めた話

日本語の「変換確定のEnter」で送信される事故を、3つの層で止めた話

変換を確定するつもりのEnterで、フォームが送られる。日本語を打つ人は全員踏む事故を、書き方・2枚重ね・機械の検査の3つの層で止めた記録です。

日本語で「せんたくそう」と打って、変換を確定するつもりでEnterを押したら、フォームが送信されてしまった。日本語を打つ人なら、一度は踏んでいるはずの事故です。

自分の案件でも同じことが何度も出て、「Enterに動作を割り当てたら、必ず変換中かどうかを見る」を決まりにしました。この記事は、その決まりの中身と、決まりだけでは足りなかった所の話です。

1何が起きるのか

日本語入力では、変換中の文字(未確定の下線がついた状態)に対して、Enterは「これで確定」の操作です。ところが、入力欄のEnterに「送信」「検索」「生成」を割り当てていると、確定のつもりのEnterがそのまま送信になります。

営業用のチャットウィジェットで直したときは、症状が2つ出ていました。確定前の文字が途中で送られてしまうことと、送信したあとに入力欄へ文字が残ること。2026-07-14 のコミットで、この2つは同じ根っこ(変換中のEnter)だと分かって、まとめて直しています。

2基本の形は1行

素のJavaScriptなら e.isComposing と e.keyCode !== 229 の両方を見ます。Reactなら e.nativeEvent.isComposing です。実際に入れているのは、たとえばこういう1行でした。

ここがポイント

if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { e.preventDefault(); send(input.value); }

— 営業用チャットウィジェット(素のJavaScript)

その8日後の 2026-07-22 には、営業リストを絞り込む検索窓にも入れました。含めたい語と除きたい語を打つ機能で、日本語を打つのは当たり前なので、最初から !e.isComposing && e.keyCode!==229 を付けています。

3Shift+Enter の改行と組み合わせる

チャット画面では、Enterで送信、Shift+Enterで改行、にしたくなります。この場合は3つを全部見ます。

  • e.key === "Enter"
  • !e.shiftKey(Shift+Enter は改行のまま残す)
  • !e.nativeEvent.isComposing(変換確定のEnterは送信にしない)

手元のコードでは、チャットの入力欄にこの3点セットが並んでいます。関数の頭で isComposing なら return してしまい、そのあとで Enter と Shift を見る書き方もあって、どちらの形でも後述の検査は通ります。textarea のEnterは改行が本来の動きなので、フォーム全体で止める側の処理を書くときは、textarea を素通しにしておかないと、改行まで死にます。

4229 だけを見る書き方は、再現ですり抜けた

ここで1つ、定番の説明を確かめようとしました。変換中のキー入力は keyCode が229で届く、というのがよく見る説明です。

CDP(ブラウザを外から操作する仕組み)の Input.imeSetComposition で変換中の状態を作ってからEnterを送ると、実際にはこう届きました(2026-08-31・Chromium・このサイトの問い合わせフォームで測定)。

Enterを送ったときの keydown の中身(Chromium・CDPで再現。keyCode は送る側で指定した値)

操作keykeyCodeisComposing止めたか
変換確定のEnterEnter13true◎止めない(これが正しい)
素のEnterEnter13false暗黙送信を止めた

この再現では、確定のEnterでも keyCode は13でした。ただ、この13は送る側(CDP)で自分で指定した値です。本物のIMEで229が届くかどうかは、まだ確かめていません。それでも、229だけに頼る書き方だと、この再現ではすり抜けました。だから isComposing を主にして、229は保険として併用しています。

5フォームでは keydown だけだと足りなかった

ここからが、今回いちばん時間を使った所です。この問い合わせフォームに、keydown のガードだけを入れて確かめたところ、確定のEnterで送信が飛びました。

理由は、単一行の input に備わっている「Enterで送信」の動き(暗黙送信)です。keydown で変換中のEnterを素通ししたら、それを止める人が誰もいなくなる。素通しにしたのは、preventDefault() を呼ぶと確定の操作そのものを壊しかねないからで、そこの判断は今も正しいと思っています。

イベントの順番を測ると、こうでした。

  • compositionstart
  • compositionupdate
  • input
  • keydown(isComposing=true・keyCode=13)
  • submit
  • compositionend

submit が compositionend より先に来ます。だから submit の処理の頭で「いま変換中か」を見れば、時間の猶予も閾値もなしに確実に落とせます。2枚重ねにしました。1枚目は keydown で、素のEnterだけ preventDefault()。2枚目は submit の先頭で composingRef.current が true なら捨てる。2026-09-01 のコミットで、問い合わせフォーム(お問い合わせ)に入っています。

落とし穴が3つありました。

  • defaultPrevented を同じイベントの中で読むと、必ず false になる。React より先に付けた自分のリスナが先に走るためで、これで「ガードが効いていない」と誤診しました。判定には、submit の回数と、実際にAPIへ飛んだ回数を使うのが安全です
  • ページがまだ動いていない状態で検証してしまうと、存在しないバグを2件観測しました。ビルドの出力を2つのプロセスで共有していて、配信中のHTMLが消えたチャンクを指していたのが原因でした。ビルドも200もスクショも通るのに、JSは1行も動いていません
  • 守りすぎると、送信ボタンを押しても送れないフォームになる。変換したまま送信ボタンを押したら、ちゃんと送れることも測りました(APIに1件、完了画面1回)

6目視では守れないので、機械で検査する

決まりを書いても、次のEnterを書くときに忘れます。なので、リポジトリ全体を検査するテストを足しました。onKeyDown やキーダウンのリスナを持つファイルを集めて、Enter を判定している箇所の前後400文字に isComposing か229があるかを見るだけの、単純なものです。

本当に落ちるのかを、別のフォルダで確かめました。isComposing の無いEnterの処理を1つ置くと、テストは「pass 0 / fail 1」で、components/Bad.tsx:2 を名指しして落ちる。!e.nativeEvent.isComposing を足した版に差し替えると「pass 1 / fail 0」。落ちるのが先、通るのが後、の順で確かめました。

最初は、role="button" や <button> のEnterまで拾って、誤検出が1件出ました。文字を打たない要素ではIMEが働かないので、除外しています。

この検査で守れるのは「書いてあるか」までです。400文字以内に isComposing という字があれば通るので、本当に効いているかの証明にはなりません。効いているかは、次の手順で人が測ります。

7入れたら通す6項目

日本語入力欄にEnterの動作を足したら、完了と言う前にこの6つを通すことにしています。

  • 変換確定のEnterで送信されない
  • メール欄でも同じ
  • 素のEnterでも送信されない(暗黙送信を止める場合)
  • textarea のEnterは改行のまま
  • ボタンのクリック・Enterで、1回だけ送信される
  • 候補チップのEnterは、挿入であって送信ではない

CDPなら、page.keyboard.insertText では変換中の状態が作れないので、Input.imeSetComposition で未確定の文字を置いてから Input.dispatchKeyEvent でEnterを送ります。送ったあと200ミリ秒ほど待たないと、結果が空のまま読めてしまいます。

8まだ分からないこと

正直に言うと、測ったのはChromiumだけです。SafariやiPhoneの実機、WindowsやAndroidのIMEでも、確定のEnterが同じ値で届くのかは、まだ分かりません。ここまでの2枚重ねは、composingRef で「変換中か」を自分で持っているので、ブラウザごとの違いには強いはずですが、強いはず、と、測った、は別の話です。

それに、CDPで作った変換中の状態は、本物のキーボードと完全に同じとは限りません。実機の確認は、まだ済んでいません。

次は、iPhoneの実機で同じ6項目を通して、結果をこのブログに追記します。それまでは、自分のフォームで「ひらがなを打つ→変換する→Enterで確定する」を、ぜひ一度試してみてください。送信されたら、そこが直す場所です。ほかの記事は ブログ にあります。

この記事を書いた人

NextCode(沖縄県宜野湾市)は、iOSアプリ・ホームページ/LP・NFCデジタル名刺・AI業務自動化を作っている開発スタジオです。ここで書いているのは、実際に作って公開したものと、その過程で詰まったことだけです。

お問い合わせフォームは24時間受け付けています。

この記事が役に立ったらシェアしてください