혼자 적어보는 노트

[React] input focus감지 / 영역 밖 클릭 시 이벤트 본문

React

[React] input focus감지 / 영역 밖 클릭 시 이벤트

jinist 2021. 12. 14. 04:19

하고싶었던 기능은
input의 focus를 감지하여 포커스가 되면 최근검색어 창이 띄워지고
검색 시 최근 검색어가 담기며, 지우기버튼을 누르면
최근검색어가 지워지는 기능이였다.

단순 onFocus / onBlur을 사용하여 최근검색어 창의 state를 true/false로 관리하려 시도했지만
focus가 된 상태에서 최근검색어 창을 누르기위해 접근하면 input의 focus가 풀리면서
당연하게도 닫기 버튼이 클릭을 할 수 없는 문제가 발생한다.


focus가 되면 state가 true가되고
focus가 되지 않은 상태에서 최근검색어 영역 밖을 클릭하면 state가 false로 되었으면 했다.

 

 

useRef를 이용한 영역 밖 클릭 감지

 

  const clickWrapp = (event) => {
    if (document.activeElement !== searchInput.current &&
    // searchInput이 focus되지 않았을 경우
    !searchWrapp.current.contains(event.target)) {
       // ref로 지정한 영역이 event.target을 포함하지 않았을 경우 코드 실행
      setSearchState({ ...searchState, searchFocus: false });
    }
  };
  useEffect(() => {
    document.addEventListener("click", clickWrapp);
  }, []);

 

useEffect로 document전체의 클릭을 감지하고

document.activeElement !== searchInput.current 를 이용하여 현재 input에 focus가 되어있는지를 확인했다.

해당 조건에 부합하면 searchFocus를 false로 바꾸었다.

 

전체코드

export default function SearchPopup() {
  const searchInput = useRef();
  const searchWrapp = useRef();
  const history = useHistory();

  const initialSearch = {
    searchKeyword: "",
    searchFocus: false,
  };

  const [searchState, setSearchState] = useState(initialSearch);
  const [historyList, setHistoryList] = useState([]);

  const handleFocusOn = () => {
    setSearchState({ ...searchState, searchFocus: true });
  };

  const clickWrapp = (event) => {
    if (
      document.activeElement !== searchInput.current &&
      !searchWrapp.current.contains(event.target)
    ) {
      setSearchState({ ...searchState, searchFocus: false });
    }
  };

  const search = (keyword) => {
    console.log("search");
    setSearchState({ ...searchState, searchKeyword: keyword });
    addHistory(keyword);
  };

  const addHistory = (keyword) => {
    if (!keyword) {
      return;
    }
    const history = { id: createNextId(historyList), keyword };
    setHistoryList([history, ...historyList]);
  };

  const removeHistory = (id) => {
    const history = historyList.filter((item) => item.id !== id);
    setHistoryList(history);
  };

  useEffect(() => {
    document.addEventListener("click", clickWrapp);
  }, []);

  return (
    <div className="search-area" ref={searchWrapp}>
      <form
        className="search-form"
        onSubmit={(event) => {
          event.preventDefault();
          search(searchState.searchKeyword);
          history.push({
            pathname: `/search/${searchState.searchKeyword}`,
          });
          searchInput.current.value = "";
        }}
      >
        <input
          ref={searchInput}
          type="text"
          onChange={(event) => {
            setSearchState({
              ...searchState,
              searchKeyword: event.target.value,
            });
          }}
          onFocus={(event) => handleFocusOn(event)}
        />
        <button>검색</button>
      </form>
      {searchState.searchFocus && (
        <div className="search-layer">
          <h1>최근검색어</h1>
          <ul>
            {historyList.length > 0 ? (
              historyList.map(({ id, keyword }) => (
                <li key={id}>
                  {keyword}
                  <button
                    onClick={(event) => {
                      removeHistory(id);
                    }}
                  >
                    닫기
                  </button>
                </li>
              ))
            ) : (
              <span>검색결과가 없습니다 </span>
            )}
          </ul>
        </div>
      )}
    </div>
  );
}

 

하지만 최근 검색어 창 안의 최근검색어 리스트에 닫기버튼을 부여해주었는데

닫기 버튼을 누르면 리스트에서 삭제되는 기능은 동작하나 닫기 클릭 시 창이 닫아져버린다.

clickWrapp()이 함께 실행되버린 것이다.

 

<button
  onClick={(event) => {
    event.stopPropagation();
    removeHistory(id);
  }}
>
  닫기
</button>

버튼의 onClick에 event.stopPropagation()을 넣어 이벤트가 전달되는 것을 막으니 해결되었다.

Comments