React에서 input 한글 입력시 onKeyDown 이슈 (문제 해결)

Devinix·2024년 3월 11일
0

[문제 해결]

목록 보기
18/29
post-thumbnail

개요

input에서 입력한 문자열 값을 배열로 관리하여 화면에 렌더링하는 기능을 구현 중이었다. input의 onKeyDown 속성을 사용해 엔터 키 입력을 감지하여 처리하고 있다. 코드를 살펴보자.

useKeyword 훅

import { Dispatch, SetStateAction, useEffect, useState } from "react";

interface IProps {
  keywordList: string[];
  setKeywordList: Dispatch<SetStateAction<string[]>>;
}

function useKeyword({ keywordList, setKeywordList }: IProps) {
  const [value, setValue] = useState("");
  const [isDisabled, setIsDisabled] = useState(false);

  useEffect(() => {
    if (keywordList.length === 10 || !value) setIsDisabled(true);
    else setIsDisabled(false);
  }, [keywordList.length, value]);

  const addKeyword = () => {
    if (keywordList.length === 10) return;
    setKeywordList((prev) => [...prev, value]);
    setValue("");
  };

  // 엔터키 입력시 addKeyword 함수를 호출
  const handleKeyPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") {
      addKeyword();
    }
  };

  const deleteKeyword = (index: number) => {
    const filteredKeywordList = keywordList.filter((_, i) => index !== i);
    setKeywordList(filteredKeywordList);
  };

  return {
    value,
    setValue,
    addKeyword,
    isDisabled,
    handleKeyPressEnter,
    deleteKeyword,
    setIsComposing,
  };
}

export default useKeyword;

Keyword 컴포넌트

import styles from "./KeywordSection.module.scss";
import AddProductDetailSection from "../addProductDetailSection/AddProductDetailSection";
import { Dispatch, SetStateAction } from "react";
import useKeyword from "../../../hooks/useKeyword/useKeyword";

interface IProps {
  keywordList: string[];
  setKeywordList: Dispatch<SetStateAction<string[]>>;
}

function KeywordSection({ keywordList, setKeywordList }: IProps): JSX.Element {
  const {
    value,
    setValue,
    handleKeyPressEnter,
    addKeyword,
    deleteKeyword,
    isDisabled,
    setIsComposing,
  } = useKeyword({
    keywordList,
    setKeywordList,
  });

  return (
    <AddProductDetailSection
      label="키워드 입력"
      limit={`(${keywordList.length}/10)`}
    >
      <div className={styles.container}>
        <section>
          <input
            type="text"
            value={value}
            onChange={(e) => {
              setValue(e.target.value);
            }}
            
            // onKeyDown 부분
            onKeyDown={(e) => {
              handleKeyPressEnter(e);
            }}
            placeholder="키워드 입력"
            className={styles.keyword}
          />
          <button
            onClick={addKeyword}
            disabled={isDisabled}
            className={styles.button}
          >
            추가
          </button>
        </section>

        <ul className={styles.user_keywords}>
          {keywordList.map((keyword, i) => (
            <li
              key={i}
              onClick={() => {
                deleteKeyword(i);
              }}
              className={styles.user_keyword}
            >
              <img src="/images/x.png" alt="x" className={styles.x_icon} />
              {keyword}
            </li>
          ))}
        </ul>
      </div>
    </AddProductDetailSection>
  );
}

export default KeywordSection;

문제 상황

영어는 별 문제가 없었는데 한글로 입력한 키워드들은 두번씩 배열에 출력되는 이슈가 발생했다.

ex) 안녕 => 안녕, 녕

원인

원인은 IME 때문이었다.

IME란?

IME(Input Method Editor)는 키보드 입력을 통해 다양한 문자 체계(예: 한글, 중국어, 일본어 등)를 입력할 수 있게 해주는 소프트웨어 도구이다. 특히 알파벳을 기반으로 하지 않는 언어를 입력할 때 필수적인 도구로, 사용자가 여러 키 입력을 조합하여 복잡한 문자와 기호를 생성할 수 있게 한다.

IME의 작동 방식

IME는 사용자가 키보드로 입력하는 단일 문자 또는 문자 시퀀스를 받아서, 그것을 해당 언어의 문자나 단어로 변환한다. 예를 들어, 한글 입력 시에는 자음과 모음을 각각 입력하고, 이를 조합하여 하나의 글자를 만든다. 이 과정에서 사용자는 여러 입력 조합을 시도할 수 있으며, IME는 이를 실시간으로 처리하여 올바른 문자를 생성한다.

IME와 관련된 이슈

입력 완료 감지

사용자가 IME를 사용하여 문자를 입력할 때, 입력이 완료되었는지 아닌지를 정확히 감지하는 것이 중요하다. compositionstart와 compositionend 이벤트를 통해 입력 시작과 완료 시점을 감지할 수 있다.

중간 입력 처리

IME를 사용하는 동안 중간 단계의 입력이 있을 수 있으며, 이러한 입력은 최종 입력 값에 포함되지 않아야 한다. 예를 들어, 'Enter' 키를 눌렀을 때 아직 조합이 완료되지 않은 입력을 처리하려고 시도하면 예상치 못한 결과가 발생할 수 있다.

해결 과정

useKeyword 훅 내에 isComposing 상태를 설정하고, setIsComposing을 return하여 input의 onCompositionStart와 onCompositionEnd 프로퍼티에 할당해주는 것으로 문제를 해결할 수 있었다.

useKeyword 훅 내부

  // useKeyword에서 관리중인 isComposing 상태
  const [isComposing, setIsComposing] = useState(false);


  const handleKeyPressEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
    // isComposing 상태면 return하여 함수 종료
    if (isComposing) return;
    if (e.key === "Enter") {
      addKeyword();
    }
  };

Keyword 컴포넌트 내부

          <input
            type="text"
            value={value}
            
            // 추가된 프로퍼티
            onCompositionStart={() => setIsComposing(true)}
            onCompositionEnd={() => setIsComposing(false)}
            onChange={(e) => {
              setValue(e.target.value);
            }}
            onKeyDown={(e) => {
              handleKeyPressEnter(e);
            }}
            placeholder="키워드 입력"
            className={styles.keyword}
          />

결론

웹 애플리케이션을 개발할 때 IME의 존재와 작동 방식을 이해하는 것은 글로벌 사용자를 위한 접근성과 사용자 경험(UX)을 향상시키는 데 중요하다. 사용자가 자신의 언어로 자연스럽게 입력할 수 있도록 지원함으로써, 애플리케이션의 접근성과 만족도를 높일 수 있다는 것을 깨달았다.

참고한 블로그

https://bobostown.tistory.com/2

profile
프론트엔드 개발

0개의 댓글