Back

onChange와 debounce 사용에서 생긴 문제점

리액트 프로젝트 내에서 어떤 검색을 할 경우 검색버튼을 누르지 않고 바로 사용자가 타이핑을 치면 검색이 이루어지게끔 하는 것을 구현해야했다. 이전에 회원가입을 하는경우 validation check를 실시간으로 하는 유명 포탈사이트에서 봤던 것과 비슷했다. TextField에 onChange 이벤트를 추가하여, 텍스트가 바뀔 때 마다 상태를 filter를 통해 걸러주는 함수를 호출하는 형태로 만들었다.
하지만, 한글의 글자 하나가 만들어지기도 전인 자음 하나만 치는 과정에서조차 매번 해당 함수를 호출하는 것이 보기가 싫었다. 자음이 섞여있는 검색조건은 항상 검색된 데이터 목록이 0개가 되며 컴포넌트는 데이터가 없다는 것을 매번 랜더링 해주는데, 좋지않은 사용자 경험을 만들 수 있다는 생각에 이를 해결하는 방법을 찾게 되었다.

여러 방법이 있었는데 그 중에서 lodash에서 구현된 debounce를 사용하였다. setTimeout 처럼 일정한 ms를 파라미터로 넘겨 해당 시간동안 기다리고 바로 함수를 호출하지 않으며, 그 기다리는 시간 안에 같은 요청이 올 경우 해당 시간을 처음부터 다시 기다려 최종적으로 한 번만 함수를 호출해주는 멋진 기능이다. 그런데 문제가 생겼다.

onChange 함수 안에서 바로 debounce를 사용하면 debounce가 원하는 방식대로 동작을 하지 않는다. 검색을 해보니 react에서 렌더링이 될 경우 함수를 매번 새로 만들게 되어서 그렇다고 한다. 이런 경우 useCallback hooks를 사용하여 보통 해결을 한다고 하는데

const onChangeSearchTextField = (e) => {
  const searchValue = e.target.value;
  setSearchValue(searchValue);
  refreshFilteredList(searchValue);
};

const refreshFilteredList = useCallback(
  debounce((searchValue) => {
    setRows(
      boardList.filter(
        (e) =>
          e.TITLE.indexOf(searchValue) !== -1 ||
          e.ADD_USER_NAME.indexOf(searchValue) !== -1
      )
    );
  }, 500),
  []
);

나같은 경우 이런식으로 useCallback으로 감싸니까 해결했다.

잘은 모르지만, 내가 이해한 것으로는 useCallback으로 함수를 감싸고 deps에 빈 배열을 넣는 방법을 통해 생성한 함수가 첫 컴포넌트 렌더링 이후에는 무조건 재사용되어 앞에서 진행한 debounce 함수에 그대로 다시 접근할 수 있어서 잘 동작이 되는 것 같다.

이 useCallback이 이전에는 그저 함수를 재사용하여 성능을 개선하기 위한 용도로만 사용이 되는줄 알았으나 동일한 함수 내부에 접근해야 하는 경우(해당 경우에는 setTimeout)에는 무조건 필요한 경우도 있다는 것을 알았다.

글 까지 남겼으니, 나중에 해당 기능을 구현할 때 삽질 안하고 빠르게 구현할 수 있도록 해야겠다.