인턴 생활을 시작한 지 벌써 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탄에서 해보도록 하겠다!