자동완성 기능 이벤트 관리하기

Imnottired·2023년 4월 29일
0

Lessons Learned

목록 보기
2/6

자동 검색 기능을 만들던 도중 오류를 발견하였다.

아래처럼 드롭다운이 내려왔을 때
남원읍을 클릭하면 받아올 수 있도록 설계하였다.

              <li
                className={styles.autocomplete_item}
                key={`${item}${index}`}
                onClick={() => {
                  setInputValue(item);
                  setSelected(-1);
                  setIsVisible(false);
                }}
                onMouseOver={() => setSelected(index)}
                style={{ background: selected === index ? "#ffab4f" : "" }}
              >
                {item}
              </li>

그런데 예상과 다르게 클릭하였을 때 클릭 이벤트가 발생하지 않았고,

    <input
          type="text"
          ref={inputRef}
          className={styles.searchBarInput}
          placeholder="동이름으로 검색해주세요"
          value={inputValue}
          onChange={handleInputChange} // 작성
          onKeyDown={handleInputKeyDown} //키보드
          onCompositionStart={() => setIsComposing(true)}
          onCompositionEnd={() => setIsComposing(false)}
          spellCheck={false}
          onFocus={() => setIsVisible(true)}
          onblur={()=> blur()}
        />
    
    const blur = () => {
    setIsVisible(false);
    setSelected(-1);

    //밖에 눌렀을떄 -1되야함
    //1의 경우의수 밖 눌렀을때
    //2의 경우의 수 안을 눌렀을떄
  };

blur 이벤트가 발생하였다.
왜냐하면 클릭하는 순간 포커스를 잃기때문이었다.

1번째 시도

              <ui
            
                onblur={()=>blur()}
                />
           

li를 감싸고 있는 ui에 blur를 설정하였다.
당연하게도 실패하였다.
focus 가능한 html 태그가 아니어서 blur가 올바르게 작동하지 않았다.

focus

focus란 웹 페이지에서 현재 선택된 요소를 나타내는 개념이지만,
가능한 요소가 있고 없고라는 것을 알게 되었다.

onblur 이벤트는 밖을 클릭했을 때 발생하는 이벤트가 아닌,
포커스가 풀렸을 때 나오는 이벤트라는 것에 대해 더 깊게 이해가 되었다.

2번째 시도

pointer-events: none;을 활용하여서
내가 클릭했을 때는 다른 이벤트를 막고, 내가 원하는 이벤트만을 통제하려고 하였다.

하지만 포커싱이 풀리자 당연하게 blur 이벤트가 발생하였다.

pointer-events: none;

pointer event란 포인터로 행하는 이벤트를 막을지 넣을지 설정하는 것이다.

3번째 시도

contentEditable를 활용하여서 수정 가능하게 만들어주고 포커싱을 활용하는 전략이다.

contentEditable 속성은 HTML5에서 추가된 속성으로, 해당 요소의 내용을 편집 가능하게 만들어줍니다.

사용해보니

위처럼 포커스가 되고 내가 원하는 방향도 진행되었지만, onblur와 onclick 둘다 동시에 발생하였고, 단순히 포커스 이벤트를 컨트롤하기 위해서 하기에는리스크가 있어서 지향하는 방향가 맞지 않았다.
나중에 기억했다가 contentEditable 필요하다면 써보겠다.

4번째 시도

vscode에 Ref를 보고 아이디어를 얻어서 이를 활용해보기로 하였다.
직접적으로 dom에 클릭 이벤트를 추가하고, 검색창과 자동완성 관련을 클릭했을 때는 발동하지 않고, 그외 부분만 발동하도록 설계하였다.


  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        divRef.current &&
        !divRef.current.contains(event.target as HTMLDivElement)
      ) {
        blur(); // 원하는 동작 수행
      }
    };

    document.addEventListener("click", handleClickOutside);

    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  }, []);
  

위처럼 마운트시 이벤트를 추가하였다 input과 dropdown을 감싸는 div에 추가하여서 위 자식 요소들을 클릭했을 때는 발동하지 않도록 contains를 활용하였다.

이를 통해 깔끔하게 클릭했을 때는 내가 원하는 값을 받아오고 밖을 눌렀을 땐 초기화 되도록 잘되었다.

문제

문제는 이벤트가 제거되지않아서 클릭만하면 이벤트가 발생한다는 문제가 생겼다.
(div 밖을 클리하는 이벤트를 만들었지만 clean up함수가 작동하지 않았다.)

 useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (
        divRef.current &&
        !divRef.current.contains(event.target as HTMLDivElement) &&
isVisiable
      ) {
        blur(); // 원하는 동작 수행
      }
    };

    document.addEventListener("click", handleClickOutside);

    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  }, [isVisiable]);

if문에 드롭다운 on/off 여부를 반환하는 문자를 넣고, 2번째 인자에 on/off 여부를 넣어도 다르지 않았다.
왜냐하면 clean up 함수가 작동하기 전에 재생성을 하기 때문에 작동하지 않았다.
그래서 이를 해결하기 위해

  useEffect(() => {
    const handleClickOutside = (event: any) => {
      if (divRef.current && !divRef.current.contains(event.target)) {
        blur(); // 원하는 동작 수행
      }
    };

    if (isVisible) {
      document.addEventListener("click", handleClickOutside);
    } else {
      document.removeEventListener("click", handleClickOutside);
    }
  }, [isVisible]);

위처럼 변경하였다.

생성과 제거가 동시에 일어나지 않고 역할 분리를 확실하게 하였다.


    const handleClickOutside = (event: any) => {
    console.log("기히")
    if (divRef.current && !divRef.current.contains(event.target)) {
      blur(); // 원하는 동작 수행
    }
  };
  

드롭다운이 내려가도 콘솔로그가 찍히던 때와 다르게

수정 이후에는 이벤트가 끝나면 제거가 되었다.


마무리

이번 기회를 통해 이벤트에 대한 이해도가 많이 생겼다.

focus에 대한 이해도, 이벤트를 추가하는 것에 대해서 많이 깨달았다.
또한 useEffect return clean up함수 활용에 대한 경험도 인상적이어서 나중에도 이를 활용할것이다.

이를 통해 다음에도 내가 원하는 이벤트 구상이 있다면 더 빠르게 가능할 것이다.

진행하면서 힘들었던 것은 구현이 다되었는데도, 이벤트가 제거되지않아서 힘들었는데, 꾸준히 포기하지않고 해낸 점이 만족스럽다.

profile
새로운 것을 배우는 것보다 정리하는 것이 중요하다.

5개의 댓글

comment-user-thumbnail
2023년 5월 6일

와우.... 결국 해내시다니 멋지십니다 ㅋㅋ

답글 달기
comment-user-thumbnail
2023년 5월 7일

끈기 ... 고생하셨습니다 !!

답글 달기
comment-user-thumbnail
2023년 5월 7일

오우 엄청 고민하셔서 결국 해결해내시다니!! 고생하셨습니다ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 5월 7일

하나 해결하면 다른 문제가 양파처럼 튀어나오는군요. 끈기있기 해결하신 점이 멋집니다!

답글 달기
comment-user-thumbnail
2023년 5월 7일

이런 트러블 슈팅 글이 제일 재밌는 거 같습니다. 잘보고 갑니다 ㅋㅋㅋ

답글 달기