
인턴 생활을 시작한 지 벌써 1달하고 2주가 지났다!
회사에서는 프론트엔드 개발자로서 UI 컴포넌트 구현 및 리팩터링을 담당하고 있다.
그 중에서 오늘 만든 날짜 선택 컴포넌트가 인상 깊어서 따로 정리해보고자 한다.
내 개인 프로젝트인 네컷사진에서 날짜 선택 기능을 구현한 적 있는데 자유롭지 못한 포맷이 꽤 아쉬던 기억이 있다.
근데 이번 Task를 통해 더욱 유연한 날짜 선택 컴포넌트를 만들 수 있게 되었다.

지금부터 유연한 DateSelection 컴포넌트를 만들러 가보자!!
// App.jsx
import DateSelection from './components/DateSelection';
function App() {
return (
<div style={{ margin: '20px' }}>
<DateSelection />
</div>
);
}
export default App;
// ./components/DateSelection.jsx
import React from 'react';
import { BsFillCalendarHeartFill } from 'react-icons/bs';
const DateSelection = () => {
return (
<div>
<input
type='text'
value=''
placeholder='placeholder'
/>
<button type='button'>
<BsFillCalendarHeartFill />
</button>
</div>
);
};
export default DateSelection;
DateSelection 컴포넌트를 사용할 App.jsx 파일과 DateSelection.jsx 파일로 분리한다.

React-icons
npm install react-icons
공식문서에서 더 많은 아이콘을 볼 수 있습니다.
// ./components/DateSelection.jsx
...
const DateSelection = () => {
const [date, setDate] = useState('');
const handleChangeDate = (e) => {
const currentValue = e.target.value;
setDate(currentValue);
};
return (
<div>
<input
type='text'
value={date}
placeholder='placeholder'
onChange={handleChangeDate}
/>
...

input에 키보드를 입력하면 date, 즉 value가 바뀌도록 한다.
// ./components/DateSelection.jsx
const DateSelection = () => {
const [date, setDate] = useState('');
const format = 'YYYY-MM-DD';
const getSeparator = () => {
const regex = /[^0-9a-zA-Z]+/;
const match = format.match(regex);
if (match) {
const symbol = match[0];
const indexes = [];
for (let i = 0; i < format.length; i++) {
if (format[i] === symbol) {
indexes.push(i);
}
}
return { symbol, indexes };
}
return { symbol: undefined, indexes: [] };
};
const separator = getSeparator();
// separator 값이 맞는지 확인
React.useEffect(() => {
console.log(separator);
// { symbol: '-', indexes: [4, 7] }
}, []);
...
날짜 형식인 format을 정한다. (추후에 format을 자유롭게 변경할 수 있도록 한다.)
getSeparator 함수를 통해 format에 사용된 날짜 구분 symbol과 그것의 위치를 담은 indexes를 구한다.
날짜 구분은 숫자나 문자가 될 수 없기 때문에 /[^0-9a-zA-Z]+/과 같은 정규표현식을 사용한다.
match는 format에서 정규표현식에 매치되는 것을 나타내며, ['-', index: 4, input: 'YYYY-MM-DD', groups: undefined]이다.
symbol은 매치되는 기호인 match[0], 즉 '-'이다.
format의 문자를 하나씩 돌려 해당 문자가 symbol 일 경우 해당 위치를 indexes에 넣는다.
정규표현식으로 매치되는 값이 없다면 빈 값을 반환한다.
반환한 값은 separator에 저장한다.
// ./components/DateSelection.jsx
const DateSelection = () => {
...
const separator = getSeparator();
const handleChangeDate = (e) => {
let currentDate = e.target.value;
if (separator.symbol && separator.indexes.length > 0) {
separator.indexes.forEach((index) => {
if (currentDate.length > index) {
currentDate =
currentDate.slice(0, index) +
separator.symbol +
currentDate.slice(index);
}
});
}
setDate(currentDate);
};
return (
...

input 값을 변경하는 handleChangeDate 함수에 separator가 존재하는 경우에 대한 기능을 추가한다.
separator.indexs를 돌려 currentDate에 symbol이 들어가야 하는지 판단한다.
만약 currentDate 길이가 해당 symbol 위치보다 크다면 당 index를 기준으로 currentDate를 나누고 그 사이에 symbol을 넣는다.
예를 들어, '20221010'과 같은 값이 들어오면 '2022-10-10'으로 바꾼다.
...
import Datetime from 'react-datetime';
const DateSelection = () => {
const [date, setDate] = useState('');
const [open, setOpen] = useState(false);
...
const handleClickButton = () => {
setOpen(!open);
};
const handleChangeCalendar = (selected) => {
const formattedDate = selected.format(format);
setDate(formattedDate);
setOpen(false);
};
return (
...
<button type='button' onClick={handleClickButton}>
<BsFillCalendarHeartFill />
</button>
{open && (
<Datetime
input={false}
timeFormat={false}
dateFormat={format}
value={date}
onChange={handleChangeCalendar}
/>
)}
</section>
);
};
export default DateSelection;
text 타입의 input은 달력을 제공하지 않기 때문에 리액트 라이브러리인 react-datetime을 사용한다.
button을 클릭하면 캘린더를 열고 닫히도록 한다.
open 일 경우 캘린더인 Datetime 컴포넌트가 보이며 필요한 props를 넣어준다.
값을 넣는 input은 앞에서 이미 만들었기 때문에 false로 지정한다.
시간 조절하는 기능은 넣지 않을 것이기 때문에 dateFormat도 false로 지정한다.
dateFormat은 위에 미리 선언한 format으로 지정한다.
Datetime 내 날짜를 클릭하면 date가 변경되도록 한다.
formattedDate는 selected, 즉 선택된 날짜가format에 맞게 포멧팅 된 값이다.
date를 formattedDate로 바꾸고 Datetime을 닫아주면 기능이 끝난다.
React-datetime
npm install --save react-datetime
공식문서에서 다양한 속성을 볼 수 있습니다.

이렇게 5단계에 걸쳐 기능을 구현해보았다.
여기에 날짜 유효성 체크와 재사용성을 위한 리팩터링을 해야하는데
그건 2탄에서 해보도록 하겠다!