이 글은 wecode에서 실제 공부하고, 이해한 내용들을 적는 글입니다. 글의 표현과는 달리 어쩌면 실무와는 전혀 상관이 없는 글일 수 있습니다.
또한 해당 글은 다양한 자료들과 작성자 지식이 합성된 글입니다. 따라서 포스팅이 틀린 정보이거나, 해당 개념에 대한 작성자의 이해가 부족할 수 있습니다.
설명하듯 적는게 습관이라 권위자 발톱만큼의 향기가 날 수 있으나, 엄연히 학생입니다. 따라서 하나의 참고자료로 활용하시길 바랍니다.
글의 내용과 다른 정보나 견해의 차이가 있을 수 있습니다.
이럴 때, 해당 부분을 언급하셔서 제가 더 공부할 수 있는 기회를 제공해주시면 감사할 것 같습니다.
이번 2차 프로젝트에서는 캘린더를 사용할 일이 있었습니다.
그런데, react-datepicker의 경우 커스텀이 쉬운 편인지, 어려운 편인지는 모르겠으나, 꽤나 까다로웠다고 생각합니다.
예를 들어, '추가적인 정보를 달력에 기입하고 싶으면 어떤 props를 주면 될까?' 같은 부분에서 말이죠.
그런데 다행히 공식 홈페이지에 잘 정리된 내용들이 많지 않은가? 하는 생각도 있습니다.
어떻게 특정 날짜를 제외할까? 하는 생각에 대해서는 특히나 그렇습니다.
어쩌면 라이브러리를 쓰는 제가 시야가 좁아진 나머지 공식문서를 더 열심히 보지 않았기 때문에 그런 상황이 일어난 것은 아닐까 싶기도 합니다.
또 다른 사이트들은 react-datepicker를 사용하나? 하고 보면 아쉽게도 프레임워크를 사용하는지 react 사이트가 아닌 경우가 다분했기에 이번 제 커스텀을 자랑해볼까 하고 글을 씁니다.
필요한 기능은 대부분 들어있습니다.
위에서 언급한 두 기능(숫자 아래 특정 데이터 추가, 특정 일자 제외) 외에 내부 함수에서 어떻게 백엔드로 Date객체를 변환해서 보내는지도 올려보겠습니다.
import React, { useState } from 'react';
import DatePicker from 'react-datepicker';
import { ko } from 'date-fns/locale';
import { CalendarWrapper } from './CalendarWrapper';
import { NextLink } from './NextLink';
import styled from 'styled-components';
import 'react-datepicker/dist/react-datepicker.css';
import { flexSet } from '../../styles/Mixins';
import { useFetch } from '../../hooks';
import { dateConversion } from './CalendarLogic';
export const Calendar = ({
priceDisplay,
excludeOn,
setCalendarOff,
stayLocation,
linkButtonText,
linkUrl,
}) => {
const [dateRange, setDateRange] = useState([null, null]);
const [startDate, endDate] = dateRange;
const [bookInfo] = useFetch('http://localhost:3000/data/CALENDAR.json', {
method: 'GET',
});
const excludeDay = bookInfo => {
let arr = [];
for (const date in bookInfo.result) {
if (bookInfo.result[date].quantity === 0) {
arr.push(new Date(date));
}
}
return arr;
};
const renderDayContents = (day, date) => {
const tooltipText = `Tooltip for date: ${date}`;
return (
<>
<span title={tooltipText}>{date.getDate()}</span>
{bookInfo.result ? (
<span title={tooltipText}>
{bookInfo.result[dateConversion(date)]
? '₩ ' +
bookInfo.result[dateConversion(date)].price.toLocaleString()
: null}
</span>
) : null}
</>
);
};
let bookDisable = excludeDay(bookInfo);
return (
<CalendarWrapper>
<Button onClick={setCalendarOff}>X</Button>
<StyledDatePicker
locale={ko}
dateFormat="yyyy-mm-dd"
selectsRange={true}
startDate={startDate}
endDate={endDate}
onChange={date => {
setDateRange(date);
}}
isClearable={true}
monthsShown={2}
renderDayContents={priceDisplay && renderDayContents}
excludeDates={excludeOn && bookDisable}
minDate={new Date()}
showDisabledMonthNavigation
inline
/>
{linkUrl && linkButtonText && (
<NextLink
linkInfo={{ stayLocation, linkUrl, linkButtonText }}
dateRange={dateRange}
/>
)}
</CalendarWrapper>
);
};
괴랄한 props가 보이시나요... 좋은 아이디어 있으면 꼭 제보해주세요.
export function dateConversion(date) {
let month = date.getMonth() + 1;
let day = date.getDate();
return `${date.getFullYear()}-${month < 10 ? `0` + month : month}-${
day < 10 ? `0` + day : day
}`;
}
export function calcDateRange(dateRange) {
let stay = new Date();
stay =
(Date.parse(dateRange[1]) - Date.parse(dateRange[0])) /
(1000 * 60 * 60 * 24) +
1;
let startDay =
dateRange[0] &&
`${dateRange[0].getFullYear()}-${dateRange[0].getMonth()}-${dateRange[0].getDate()}`;
let endDay =
dateRange[1] &&
`${dateRange[1].getFullYear()}-${dateRange[1].getMonth()}-${dateRange[1].getDate()}`;
return [startDay, endDay, stay];
}
이 로직은 어떻게 Date 객체로 변환해서 원하는 형태 (ex: yyyy-mm-dd)로 보내줄 것인지 conversion하는 로직과 두 Date 사이의 stay, 일수를 계산하는 로직입니다.
데일리 호텔 누가 낸 아이디어일까...!
import styled from 'styled-components';
import 'react-datepicker/dist/react-datepicker.css';
import { flexSet, fullScreen } from '../../styles/Mixins';
export const CalendarWrapper = styled.div`
position: fixed;
${fullScreen}
background-color: white;
top: 0px;
z-index: 99;
> div {
${flexSet('center', 'center')}
width: 100%;
height: 100vh;
.react-datepicker {
${flexSet('center', 'center', 'column')}
width: 100%;
max-width: 1180px;
border: none;
> button {
display: none;
}
.react-datepicker__month-container {
${flexSet('center', 'center', 'column')}
width: 100%;
/* height: 30vh; */
margin: 0px;
&:last-child {
.react-datepicker__header {
.react-datepicker__day-names {
display: none;
}
}
}
.react-datepicker__header {
${flexSet('center', 'center', 'column')}
width: 100%;
border: none;
background-color: transparent;
.react-datepicker__current-month {
${flexSet('center', 'center')}
font-size: 25px;
margin: 10px 0 30px 0;
}
.react-datepicker__day-names {
${flexSet('space-around', 'center', 'row')}
width: 100%;
padding-bottom: 20px;
border-bottom: 1px solid lightgray;
font-size: 20px;
.react-datepicker__day-name {
&:first-child {
color: red;
}
&:last-child {
color: red;
}
}
}
}
.react-datepicker__month {
${flexSet('center', 'center', 'column')}
width: 100%;
.react-datepicker__week {
${flexSet('space-around', 'center', 'row')}
width: 100%;
font-size: 25px;
.react-datepicker__day--keyboard-selected {
background-color: #ff7675;
color: black;
}
.react-datepicker__day--in-selecting-range {
background-color: transparent;
color: black;
}
.react-datepicker__day--in-range {
background-color: #ff7675;
opacity: 0.5;
}
.react-datepicker__day--selecting-range-start,
.react-datepicker__day--range-start,
.react-datepicker__day--range-end {
background-color: #ff7675;
color: black;
opacity: 1;
}
.react-datepicker__day--outside-month {
background-color: transparent;
}
.react-datepicker__day--today {
border: 1px solid #ff7675;
}
.react-datepicker__day--disabled {
background-color: white;
}
.react-datepicker__day {
${flexSet('center', 'center', 'column')}
border-radius: 0px;
width: 168.5px;
height: 75px;
margin: 5px 0;
}
.react-datepicker__day--weekend {
color: red;
}
}
}
}
}
}
`;
이건 단순한 css 커스텀입니다.
이 코드는 사전에 말씀드리지만, 그냥 제가 지역 data를 받지 않으면 Calendar 내에서 Pagenation이 일어나지 않게 하기 위해 작성중인 로직입니다.
따라서, NavLink 파트는 본문의 Calendar에서 제거해도 무방합니다.
import React from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
export const NextLink = ({ linkInfo, dateRange }) => {
const { stayLocation, linkUrl, linkButtonText } = linkInfo;
return (
<StyledLink to={stayLocation ? linkUrl : ''}>
<NextStepButton
disabled={dateRange[1] !== null || stayLocation ? false : true}
onClick={() => {
console.log(dateRange);
}}
>
{linkButtonText}
</NextStepButton>
</StyledLink>
);
};
const StyledLink = styled(Link)`
display: flex;
position: absolute;
bottom: 20px;
left: 50%;
transform: translate(-50%, 0);
`;
const NextStepButton = styled.button`
width: 70vw;
height: 50px;
border: none;
border-radius: 5px;
background-color: #ff7675;
color: white;
&:disabled {
background-color: lightgray;
color: black;
}
`;
라이브러리 하나 쓰기 쉽지 않다.
이렇게 오늘 글의 총평을 내릴 수 있겠습니다.
직접 구현하는 것과 라이브러리 사용 사이에서 어떤 것이 효율적인지 때로는 고민하는 사람이 되어야겠습니다.
datepicker는 애매한 그 사이에 있지 않을까? 싶습니다.
그럼 다음 글로 뵙겠습니다. 감사합니다.
회사에서 캘린더 추가하느라고 검색해서 들어와봤는데 와보니 동우님 블로그네요 ㅋㅋ 잘 계시죠?! ㅋㅋ 7월5일에 동창회때 뵈요