[2차 프로젝트 리팩토링] (2) [Airbnb react-dates]검색 페이지 기간 선택 달력추가하기

GY·2022년 3월 2일
1

리액트

목록 보기
47/54
post-thumbnail
post-custom-banner

기존에는 완성되어 있지 않은 기능이었기 때문에, 새로 추가했다.
아직 달력 스타일을 추가로 커스터마이징하지는 않았지만, 기능 구현에 우선 집중했다.


이렇게 체크인, 체크아웃 버튼을 누르면 달력이 나오고, 달력에서 선택한 기간이 체크인 체크아웃 칸에 표시되어야 한다.

하지만 react-dates 라이브러리 공식문서를 살펴보니 라이브러리 내에서 별도로 props를 주어 이 인풋 레이아웃을 바꿀 수는 없는 것 같았다.

달력에서 받아온 선택한 기간이 원하는 레이아웃안에 표시되도록 만들어보자.


인풋창 숨기기

일단 체크인과 체크아웃을 분리하고 싶은데, 기본 라이브러리 레이아웃은 붙어 있다. 인풋 창 자체를 스타일을 override하여 숨겨주었다.

const CheckInOut = styled.div`
  display: flex;
  align-items: center;

  .DateInput {
    display: none;
  }
`;

인풋창을 없애고 원하는 달력과 기존에 구현했던 레이아웃만 남겨두었다.

원하는 영역 클릭시 달력이 나타나도록 연결하기

라이브러리에서 제공하는 인풋창을 클릭해야 달력이 열리지만, 이 인풋창을 숨겼기 때문에 달력을 열 수 없다.
대신 내가 구현해놓은 레이아웃을 클릭하면 달력이 나타나도록 만들어야 한다.

달력 표시 여부를 결정하는 props가 무엇인지, 공식문서에서 확인해보자.

Airbnb react-dates 공식문서를 참고하면 다음과 같이 나와있다.

DateRangePicker
...(생략) Similarly, you can control which input is focused as well as calendar visibility (the calendar is only visible if focusedInput is defined) with the focusedInput and onFocusChange props as shown below.

Here is the minimum REQUIRED setup you need to get the DateRangePicker working:

<DateRangePicker
  startDate={this.state.startDate} // momentPropTypes.momentObj or null,
  startDateId="your_unique_start_date_id" // PropTypes.string.isRequired,
  endDate={this.state.endDate} // momentPropTypes.momentObj or null,
  endDateId="your_unique_end_date_id" // PropTypes.string.isRequired,
  onDatesChange={({ startDate, endDate }) => this.setState({ startDate, endDate })} // PropTypes.func.isRequired,
  focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
  onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired,
/>

기본적으로 요구되는 props중 focusedInput에 따라 달력표시여부가 결정된다고 한다.
그리고 이 focusedInput은 onFocusChange에 전달한 함수에 의해 업데이트 된다.
onFocusChange에서 focusedInput을 콘솔로 출력해보면 인풋창과 날짜를 클릭할 때마다 startDate, endDate, null로 값이 업데이트되는 것을 볼 수 있다.
그리고 다음과 같은 로직임을 확인할 수 있었다.

  1. 인풋창을 클릭하면 focusedInput은startDate가 된다. 달력이 표시되고 시작 날짜를 클릭할 수 있게 시작날짜 input에 focus된다.
  2. 시작날짜를 클릭하면 focusedInput은 endDate가 된다. 끝 날짜를 선택할 수 있게 된다.
  3. 예약 끝 날짜를 선택하면 focusedInput은 null이 된다. 이 때 달력이 사라진다.

그렇다면 원하는 영역을 클릭했을 때 이 focusedInput을 변경해주면 달력을 표시 여부를 변경할 수 있다!

props로 넘겨주었다

export default function FilterDay() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const handleDatesChange = ({ startDate, endDate }) => {
    setStartDate(startDate);
    setEndDate(endDate);
  };
  const [focusedInput, setFocusedInput] = useState(null);
  
return (
    <CheckInOutInput
    	onClick={() => setFocusedInput('startDate')}
      //원하는 인풋창을 눌렀을 때 focusedInput 값을 변경해 달력표시여부를 업데이트 한다.
    	placeholder="체크인"
		aria-label="체크인"
		value={startDate ? startDate.format('YYYY-MM-DD') : checkinDate}
    />
	<CheckInOutTitle>체크아웃</CheckInOutTitle>

	<CheckInOutInput
		onClick={() => setFocusedInput('endDate')}
		placeholder="체크아웃"
		aria-label="체크아웃"
		value={endDate ? endDate.format('YYYY-MM-DD') : checkoutDate}
        />

        <CalendarS
          startDate={startDate}
          endDate={endDate}
          handleDatesChange={handleDatesChange}
          focusedInput={focusedInput}
          //하위 달력컴포넌트는 focusedInput 상탯값을 전달받고,
          //업데이트되는 값에 따라 달력 표시 여부가 변경된다.
          setFocusedInput={setFocusedInput}
        />

달력 컴포넌트 설정하기

달력 컴포넌트는 부모 컴포넌트에서 변경되는 state를 props로 전달받는다. 다음과 같이 설정했다.

import React, { useEffect, useState } from 'react';
import 'react-dates/initialize';
import {
  DateRangePicker,
} from 'react-dates';
import 'react-dates/lib/css/_datepicker.css';

function CalendarS({
  startDate,
  endDate,
  handleDatesChange,
  focusedInput,
  setFocusedInput,
}) {

  return (
    <div className="App">
      <DateRangePicker
        startDatePlaceholderText="체크인"
        endDatePlaceholderText="체크아웃"
        startDateTitleText="체크인"
        endDateTitleText="체크아웃"
        startDate={startDate}
        endDate={endDate}
        onDatesChange={handleDatesChange}
        focusedInput={focusedInput}
        onFocusChange={focusedInput => setFocusedInput(focusedInput)}
        noBorder={true}
        small={true}
      />
    </div>
  );
}

moment객체 날짜 포맷팅

이런, 이상한 숫자가 찍힌다. 아마도 react-dates에서는 momment로 날짜를 다루다 보니 뭔가 가공이 되지 않았기 때문인 것 같다.

기존 라이브러리의 인풋창에 들어오는 날짜 value는 이것이 계산된 값이므로 그대로 받아오거나, props로 새로운 state에 담아 사용할 수 있다면 좋겠지만 이것에 접근하는 방법은 따로 없는 것 같았다.

그러면, 내가 계산해서 만드는 수 밖에!

콘솔에 찍어보니 각각 startDate와 endDate는 momment객체가 담긴다.

각각 value에 날짜가 찍힐 수 있도록 날짜 형식으로 포맷팅해 value를 넣어주었다.
단, 모달창에서 선택한 날짜가 쿼리스트링으로 입력된 후 페이지 이동이 되었다면 그 날짜가 기본값으로 설정이 되어야 하기 때문에 startDate가 없을 경우 쿼리스트링에서 가져온 날짜를 표시하도록 했다.

        <CheckInOutTitle>체크인</CheckInOutTitle>
        <CheckInOutInput
          onClick={() => setFocusedInput('startDate')}
          placeholder="체크인"
          aria-label="체크인"
          value={startDate ? startDate.format('YYYY-MM-DD') : checkinDate}
        />
        <CheckInOutTitle>체크아웃</CheckInOutTitle>

        <CheckInOutInput
          onClick={() => setFocusedInput('endDate')}
          placeholder="체크아웃"
          aria-label="체크아웃"
          value={endDate ? endDate.format('YYYY-MM-DD') : checkoutDate}
        />

마찬가지로 쿼리스트링 또한 날짜 형식으로 포맷팅해서 넣어주었다.

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


Reference

profile
Why?에서 시작해 How를 찾는 과정을 좋아합니다. 그 고민과 성장의 과정을 꾸준히 기록하고자 합니다.
post-custom-banner

0개의 댓글