숙박 플랫폼 리팩토링 - 기간선택 쿼리스트링 입력 시 무한 루프에 빠지는 오류 해결하기

GY·2022년 3월 11일
0

리액트

목록 보기
49/54

URI창과 달력 인풋 값을 고려해야 했다.

문제

이미 한번 리팩토링을 했던 코드였는데, 이 때는 checkin, checkout을 키로 바로 쿼리스트링을 넣어주었었다.
이제는 dates를 키로 dates=checkin='2022-04-23'&checkout=...의 형태로 넣어야 하므로 다른 로직이 필요했다.

  const checkinDate = URLSearch.get('checkin');
  const checkoutDate = URLSearch.get('checkout');
  const handleDatesChange = ({ startDate, endDate }) => {
    setStartDate(startDate);
    setEndDate(endDate);
  };

  useEffect(() => {
    startDate && URLSearch.set('checkin', startDate.format('YYYY-MM-DD'));
    endDate && URLSearch.set('checkout', endDate.format('YYYY-MM-DD'));
    navigate(`/list?` + URLSearch.toString());
  }, [focusedInput]);

구현해야 했던 사항

  • 모달창에서 기간을 선택하면 URL창에 쿼리스트링이 입력되고, 검색 페이지의 숙박 기간에도 입력되어야 한다.
  • 검색 페이지에서 숙박기간을 선택하면 URL창에 쿼리스트링이 입력되어야 한다.
  • 검색 페이지에서 다시 모달창을 띄워 기간을 선택하면, 검색 페이지의 숙박 기간에 입력되어야 한다.
  • 검색 페이지의 숙박기간을 선택할 때, 달력에서 체크인날짜와 체크아웃날짜 각각을 입력할 때마다 실시간으로 쿼리스트링이 업데이트 되어야 한다.

어려웠던 점 - 제약 조건

  • 달력 라이브러리 사용
    airbnb에서 제공하는 react-dates 라이브러리를 새로 도입했다. 기존에 사용했던 라이브러리는 hype-server인데...교체한 이유는 다음과 같다.
  • 업데이트 및 관리가 잘 되고 있는 라이브러리
  • 예약 불가능한 날짜 처리가 가능
  • 커스터 마이징이 가능한 항목이 더 많음

그런데 의외의 제약 사항이 있었다.
이 달력 라이브러리는 다음과 같은 원리로 작동된다.
달력을 클릭할 때마다 focusedInput이라는 prop이 각각 startDate, endDate, null로 변경된다. 처음 startDate일 때 달력 중 한 날짜를 클릭하면 예약 시작 날짜로 선택되며, endDate일 때 선택한 날짜는 예약 마지막 날짜가 되어 예약 구간이 표시된다. null일 때 달력이 사라진다.

각각 시작날짜와 끝날짜는 startDate와 endDate에 업데이트 된다.
그런데 한번에 객체 항목으로 넣게 되면 하나는 null로 업데이트 되기 때문에... 따로 따로 관리해야 했다.

문제

기존

제대로 동기화가 되지 않았던 문제

approach 1

URI 변경시마다

  • 다시 모달창에서 기간을 선택하면 URL창에는 반영이 되지만, 달력에는 반영이 되지 않는다.

approach 2

useQueryStringObject에서 관리하는 상탯값에 업데이트를 하고, url창이 변경될 때마다

url창의 주소가 변경될 때마다 dates 키의 값으로 들어간 쿼리스트링 값을 객체로 변환하여 상탯값으로 업데이트 한다.

이 상탯값에 따라 url창, 달력의 인풋창과 선택값이 업데이트 된다.

const URLSearch = new URLSearchParams(location.search)
const dates = URLSearch.get('dates')

  useEffect(() => {
    const newDates = parseQueryIntoObject(dates);
    setSelectedListObject(newDates);
  }, [location.search]);

달력의 focusedInput이 바뀔 때마다 실행하는 함수 prop인 onFocusChange에 handleDatesChange함수를 전달해주었다. 달력에서 날짜를 선택할 때마다 달력 인풋창에 입력되는 값이 표시되도록 startDate, endDate 상탯값을 업데이트 해주었다.

  const handleDatesChange = ({ startDate, endDate }) => {
    startDate &&
      setSelectedListObject({
        ...selectedListObject,
        checkin: startDate.format('YYYY-MM-DD').toString(),
      });

    endDate &&
      setSelectedListObject({
        ...selectedListObject,
        checkout: endDate.format('YYYY-MM-DD').toString(),
      });
    setStartDate(startDate);
    setEndDate(endDate);
  };

react-dates는 moment객체로 값을 반환하기 때문에 날짜형태로 변환하기 위해서 format메서드를 사용해주었다. 하지만 값이 없을 경우 에러가 나기 때문에 각 각 값이 있을 때만 로직을 처리하기 위해 startDate, endDate && 연산자를 추가했다.

좋지 않은 부분은 각각 두개의 상탯값을 동시에 관리하게 되었다는 점이다.

그리고 검색페이지에서 달력을 클릭할 때마다 실시간으로 url창의 쿼리스트링도 업데이트되어야 하기 때문에, 달력 라이브러리에 prop으로 전달하는 focusedInput값이 바뀔 때마다 변경되는 상탯값을 쿼리스트링으로 변환해 url창에 입력하도록 만드렁ㅆ다.

  useEffect(() => {
    parseObjectToSearchParams();
  }, [focusedInput]);

문제, 무한 루프에 빠져버렸다!

approach 3

  • 일단, 이 영역은 다른 항목과 달리 state로 관리할 필요가 없었다. 리렌더링을 통해 화면에 변화한 값을 표시해야 할 일이 없었기 때무이다. 이를테면 체크박스나 슬라이더는 상탯값이 변화함에 따라 체크 여부나 슬라이드 위치를 반영해 보여주어야 하지만, 이 영역은 그런 부분이 없었다. state를 업데이트 하면서 무한 루프에 빠졌으므로 일반 객체로 바꾸어주었다.

url창이 변화할 때마다 숙박 기간에 표시될 수 있도록 쿼리스트링을 객체로 바꾸어 값들을 넣어주었다.

  useEffect(() => {
    const newDates = parseQueryIntoObject(dates);
    setStartDate(moment(newDates.checkin));
    setEndDate(moment(newDates.checkout));
  }, [location.search]);

그리고 달력에서 각 날짜를 선택할 때마다 이 객체에 선택된 날짜를 넣어주고, 동시에 쿼리스트링으로 바꾸어 url창을 업데이트 해주었다.

  const handleDatesChange = ({ startDate, endDate }) => {
    const queryString = URLSearch.get('dates');
    const queryObject = parseQueryIntoObject(queryString);

    if (startDate) {
      queryObject.checkin = startDate.format('YYYY-MM-DD');
      if (endDate) {
        queryObject.checkout = endDate.format('YYYY-MM-DD');
      }
      parseObjectToSearchParams(queryObject);
      setStartDate(startDate);
      setEndDate(endDate);
    }

다른 검색 항목의 경우 체크박스, 슬라이더와 같이 리렌더링해 화면에 업데이트된 사항을 표시해야 했지만, 숙박 기간을 선택하는 이 컴포넌트에서는 별도로 필요가 없었기에 위의 코드로도 충분히 작동했다.

더 고민해보고 싶은 점

  • startDate, endDate 조건문 : 라이브러리를 사용하다보니 정해진 props를 활용해야 해 조건문이 지저분해졌다. startDate과 endDate은 하나의 값이 들어올 때 하나가 null로 들어오기 때문에 하나의 객체로 묶어 관리할 수 없었기 때문에 위와 같이 코드를 작성했다. 아직까지 react-dates을 사용하는 다른 사람들의 코드를 찾아봤을 때 이 부분을 해결한 코드는 보지 못했는데, 더 좋은 방법을 찾을 수 있으면 좋겠다.
  • 로직 자체에 대한 고민 : 결국 공통로직을 재사용하진 못했다. 굳이 필요가 없다면 사용하지 않아도 되지만, 조금 더 깔끔하게 공통적으로 적용할 수 있는 방법은 없을지 고민해보고 싶다.
profile
Why?에서 시작해 How를 찾는 과정을 좋아합니다. 그 고민과 성장의 과정을 꾸준히 기록하고자 합니다.

0개의 댓글