Auto Complete(React)

heyj·2022년 2월 12일
3
post-thumbnail

Auto Complete 만들기

1. 만들고 싶은 기능 정리하기

auto completing은 검색기능을 만들 때 필수적으로 구현해야 하는 기능이다. 알파벳을 하나 이상 input에 입력하면 그 입력값으로 시작하는 추천 검색어가 랜더링돼 user에게 보여지는 것이 핵심이다.

  1. 입력 알파벳이 하나 이상이면 suggestion list 랜더링하기

  2. 입력 알파벳이 하나 이상이면 clear button이 보이도록 하기

  3. suggestion을 클릭하면 그 값을 input value로 세팅하기

  4. input에 알파벳을 입력 후 suggestion을 클릭하지 않은 상태에서, input과 suggestion 외부에서 클릭이벤트가 발생했을 때
    4-1. suggestion list가 닫히도록 하기
    4-2. input값은 입력값 그대로 남아 있게 하기

  5. clear button 클릭시 input값 비우기, suggestion list 닫기

세부적으로 5개 기능을 정리해 code를 작성하기 시작했다.

2. Auto complete Component

컴포넌트 내에서 상태관리가 필요한 것은 input의 text value와 suggestion값, 그리고 search가 시작됐는지 확인하기 위한 값 3가지였다.

const [suggestions, setSuggestions] = useState([]);
const [text, setText] = useState("");
const [isAutoCompleting, setIsAutoCompleting] = useState(false);

2-1. 입력 알파벳이 하나 이상이면 suggestion list 렌더링하기

컴포넌트 초기 렌더링 element들을 작성했다.

return (
  <div
    className={styles.autoCompleteBox}
    >
    <input
      value={text}
      type="text"
      onChange={handleTextChanged}
      onClick={() => setIsAutoCompleting(true)}
      />
    {suggestions.length > 0 && renderSuggestions()}
  </div>
  );

input에 값이 들어오면 onChange 함수를 이용해 textvalue를 세팅해주고, 이 값을 이용해 data에 들어있는 값들과 같은 것들을 찾아준다.

빈 배열의 list를 만들고 textvalue값으로 data에 있는 값들을 조회해 list에 넣어준다. suggestions의 length가 1 이상일 경우 랜더링이 일어나도록 했으므로 이 list배열을 suggestion에 세팅해준다.

  const handleTextChanged = e => {
    let suggestedDataList = [];
    let textValue = e.target.value;

    if (textValue !== "") {
      const regex = new RegExp(`^${tempValue}`, "i");
      suggestedDataList = initData
        .filter(e => regex.test(e.label))
    }
    setSuggestions(suggestedDataList);
    setText(textValue);
  };

만약 data에 같은 알파벳으로 시작하는 추천검색어가 많을 경우, 알파벳 순서대로 정렬해주기 위해 sort를 이용했다. MDN에 아주 친절한 예제가 나와 있다.

MDN sort()

const items = [
  { name: 'Edward', value: 21 },
  { name: 'Sharpe', value: 37 },
  { name: 'And', value: 45 },
  { name: 'The', value: -12 },
  { name: 'Magnetic', value: 13 },
  { name: 'Zeros', value: 37 }
];

// value 기준으로 정렬
items.sort(function (a, b) {
  if (a.value > b.value) {
    return 1;
  }
  if (a.value < b.value) {
    return -1;
  }
  // a must be equal to b
  return 0;
});

// name 기준으로 정렬
items.sort(function(a, b) {
  let nameA = a.name.toUpperCase(); // ignore upper and lowercase
  let nameB = b.name.toUpperCase(); // ignore upper and lowercase
  if (nameA < nameB) {
    return -1;
  }
  if (nameA > nameB) {
    return 1;
  }

  // 이름이 같을 경우
  return 0;
});

그대로 프로젝트에 적용해줬다.

const compareLabel = (a, b) => {
  const labelA = a.label.toUpperCase();
  const labelB = b.label.toUpperCase();

  if (labelA < labelB) return -1;
  if (labelA > labelB) return 1;

  return 0;
};

if (textValue !== "") {
  const regex = new RegExp(`^${tempValue}`, "i");
  suggestedDataList = initData
    .filter(e => regex.test(e.label))
  	.sort(compareLabel)
}

2.2 Clear button

입력값과 추천검색어 랜더링을 초기화할 버튼을 만들어줬다.

 const clear = () => {
    setIsAutoCompleting(false);
    setText("");
    setSuggestions([]);
 };

{isAutoCompleting &&
  <button
    onClick={clear}
    >
    x
  </button>}

2.3 Suggestion값을 input값으로 세팅하기

알파벳을 입력하면 추천검색어가 나오기 시작한다. 원하는 검색어를 마우스로 클릭하면 sugggestion list는 감추고 input의 textContent로 세팅되도록 했다.

const selectSuggestion = value => {
    setSuggestions([]);
    setText(value);
  }

<ul data-testid="test-suggestionlist">
  {suggestions.map((item, index) =>
                  <li
                     key={index}
                     onClick={() => selectSuggestion(item.label)}
                     >
                     {item.label}
                   </li>
                  )}
</ul>

3. 예외처리

suggestion 선택이전, 외부에서 클릭이벤트가 발생했을 때 검색 중단하기

input에 알파벳을 입력 후 suggestion을 클릭하지 않은 상태에서, input과 suggestion 외부에서 클릭이벤트가 발생했을 때를 위한 예외처리가 필요했다. 검색입력어는 input에 그대로 남아 있지만 suggestion list는 닫혀야 했다.

input과 suggestion 외부에서 이벤트가 발생하므로, 그 둘을 감싸고 있는 부모 태그에 ref를 걸어두고 클릭 이벤트가 발생하는 곳이 부모 태그 외부인지 확인했다.

검색어 search가 시작됐는지를 확인하는 상태변수를 false로 바꿔 search를 중단하도록 했다.

const handleClick = e => {
  if (boxRef.current && !boxRef.current.contains(e.target)) {
    setIsAutoCompleting(false);
  }
}

return (
	<div className={styles.autoCompleteBox} ref={boxRef}
)

4. 완성

추후에 keydown 이벤트도 추가해줘야 겠다.

0개의 댓글