최근검색어 기능 추가

jiseong·2022년 3월 9일
0

T I Learned

목록 보기
191/291

기존의 음악을 검색하기 위해 사용하는 검색바에 그동안의 검색기록들을 참고할 수 있는 최근검색어 기능을 추가하고자 했다.

Recoil을 사용하고 있어 가장 먼저 떠오른 방식은 localStorage를 활용한 방식이였고 Recoil의 Atom Effects를 사용해서 비교적 유저의 검색기록을 쉽게 저장하고 삭제할 수 있었다.

다음은 검색기록을 추가하고 삭제하는 코드이다.

검색기록을 추가하는 경우는 기존의 검색기록에서 중복되는 검색어를 삭제해주고 새로운 검색어를 맨 앞에 두는 방식으로 구현하였다.

const [serachHistory, setSearchHistory] = useRecoilState(searchHistoryState);

setSearchHistory((prevHistory) => customSearchHistory(keyword, prevHistory));

export const customSearchHistory = (newItem: string, list: string[]) => {
  const newList = [newItem, ...list.filter((item) => item !== newItem)];

  return newList.slice(0, 5);
};

삭제의 경우는 삭제할 인덱스를 넘겨주는 방식으로 삭제하였다.

const [serachHistory, setSearchHistory] = useRecoilState(searchHistoryState);

const delSearchHistory = (idx: number) => {
  setSearchHistory((prevHistory) => [
    ...prevHistory.slice(0, idx),
    ...prevHistory.slice(idx + 1),
  ]);
};

검색어 관리는 비교적 쉬웠지만 문제는 모달과 같은 최근검색어 박스를 어떻게 보여주고 삭제할지가 문제였다.

우선, 검색을 하고 있을 때는 보여주면 안되기 때문에 상태로 관리하고 있는 query가 빈값일 때만 보여주기로 했다.

const [query, setQuery] = useState(keyword);

return (
	<SearchHistory isShow={query === ''} />
);

하지만 다음과 같이 query로만 최근검색어 박스를 on/off 할시 검색창에 focus가 되지 않아도 보이기 때문에 추가적인 상태를 두어 focus가 되었을 때만 on이 되도록 하였다.

const [isShow, setIsShow] = useState(false);

return (
  <Container>
    <SearchFormWrapper onSubmit={(e) => handleSubmit(e)}>
      <InputBox
        type="text"
        placeholder="검색어를 입력해주세요"
        value={query}
        onChange={handleChange}
        onFocus={() => setIsShow(true)}
        />
    </SearchFormWrapper>
    <SearchHistory
      isShow={isShow && query === ''}
      />
  </Container>
);

위의 방식대로 한다면 사용자가 input창에 focus되었을 때, 최근검색어 박스가 on이 되면서 잘 작동한다. 하지만 또 다른 문제점이 있는데 focus가 된 상태에서 query를 입력하지 않고 다른 작업을 하게 된다면 그대로 창이 열린상태로 진행되어버린다. 그래서 처음에는 onblur 이벤트를 이용해서 focus가 아웃되었을 때 끄려고 했지만 최근검색어 박스안에서 클릭하는 작업들을 한다면 focus가 나가버리기 때문에 창이 꺼져버린다.

그래서 해당 컴포넌트가 mount되었을 때, mousedown이벤트를 전역에 달아두고 버블링을 활용하여 다음과 같이 범위 바깥을 클릭하게 된다면 끄는 방식으로 구현하게 되었다.

useEffect(() => {
  const handleOutofRange = (e: MouseEvent) => {
    if (!inputRef.current?.contains(e.target as Node)) {
      setIsShow(false);
    }
  };

  window.addEventListener('mousedown', handleOutofRange);

  return () => {
    window.removeEventListener('mousedown', handleOutofRange);
  };
}, []);

여기서 click 이벤트가 아닌 mousedown 이벤트를 달아둔 이유는 마우스를 꾹 누른상태에서 input창의 바깥에서 놓아버리면 바깥으로 인식하기 때문에 위와 같은 상황을 방지하려고 했다.

최종적으로 구현된 그림은 다음과 같다.

0개의 댓글