😺

input入力中に変換未確定の状態でフォーカス移動すると文字が二重入力される

2024/11/21に公開

背景

以前、チームでハッカソンに参加した時に、コピっと!というサービスを作成しました。
コピっと!の要件の中に、文字コードを入力するフォームがありました。

しかし、Vercel でデプロイした後、スマホや他のユーザーの PC でテストをすると、日本語入力の時に 2 文字入力されてしまうバグが認められました。

コード

sample
const inputRef: React.RefObject<HTMLInputElement>[] = [
  useRef<HTMLInputElement>(null),
  useRef<HTMLInputElement>(null),
  useRef<HTMLInputElement>(null),
  useRef<HTMLInputElement>(null),
  useRef<HTMLInputElement>(null),
  useRef<HTMLInputElement>(null),
];

const bsFunction = (event: KeyboardEvent) => {
  if (event.key === "Backspace" && inputIndex > 0) {
    inputRef[inputIndex - 1].current?.focus();
    setIndex(inputIndex - 1);
  }
};

useEffect(() => {
  document.addEventListener("keydown", bsFunction, false);
  return () => {
    document.removeEventListener("keydown", bsFunction, false);
  };
}, []);

return (
  <>
    {[...Array(6)].map((_, i) => (
      <input
        maxLength={1} // 入力を1文字に制限
        key={i}
        autoFocus={i === 0}
        value={code[i]}
        type="text"
        autoComplete="off"
        autoCorrect="off"
        autoCapitalize="none"
        spellCheck="true"
        ref={inputRef[i]}
        onChange={(e) => {
          const value = e.target.value;

          // 入力が1文字でない場合には、処理をスキップ
          if (value.length > 1) return;

          const codeArray = [...code];
          codeArray[i] = value;
          setCode(codeArray);

          // 最後の入力欄では次のインプットにフォーカスを移さない
          if (value !== "" && i < 5) {
            inputRef[i + 1]?.current?.focus();
          }
        }}
        onKeyDown={(e) => {
          if (e.key === "Backspace" && code[i] === "") {
            // 現在のinputが空でbackspaceキーが押された場合、前のinputに移動
            if (i > 0) {
              (inputRef[i - 1]?.current as HTMLInputElement)?.focus();
            }
          }
        }}
        style={{
          width: "20px",
          height: "35px",
          textAlign: "center",
          fontSize: "20px",
          border: "none",
          borderRadius: "5px",
          outline: "none",
        }}
      />
    ))}
  </>
);

原因

OS 環境によって、日本語の IME(入力方式エディタ)を使用している場合、入力中に変換が未確定の状態でフォーカスが移動すると、未確定の文字列が二重に入力される問題が発生します。これは、onChange イベントが未確定の入力中にも発火し、その結果、入力が二重に処理されてしまうためです。

とのことです(by ChatGPT)。また、調べるとこちらの記事がヒットしました。こちらでは Windows 版で起こるバグとされていますが、今回 Mac でも場合によって発動することが確認されています。

対策として、onChange イベントが未確定の入力中に発火しないようするというのが真っ先に思いつきますが、そうすると 1 文字目の入力ができません。
また、先ほどの記事では、フォーカス時に全選択状態を一旦解除して、setTimeout でちょっとほとぼりをさましてから、再度全選択状態にすることで対策していました。しかし、今回の場合、6 桁あり、1 つ入力すると自動的にフォーカスが切れて次のフォームに移るというものなので、それでは対策ができません

結果

対策を諦め、フォームを 6 つに分けるのをやめました。

1 つにしちゃいました。技術の敗北です。他に正攻法で良いやり方を思いついた方、是非教えてください。

 
 
 
 

以上です。

Discussion