캘린더 구현하기: react-native-calenders
리액트 네이티브에서는 `react-native-calenders`라이브러리로 간단하게 가져와서 활용할 수 있고 가장 널리 이용되는 방법이다.
https://github.com/wix/react-native-calendars
캘린더 라이브러리 설치하기
설치하기
npm install react-native-calendars
사용하기
라이브러리를 import하고, `<Calender />`컴포넌트로 불러와 사용한다. 이 라이브러리가 제공하는 다양한 프로퍼티로 디자인 또는 기능을 조작할 수 있다.
- onDayPress : 달력의 특정 날짜를 클릭했을 때 호출되는 함수
- markedDates : 달력의 틀정 날짜에 시각적인 마크를 추가하는 속성
import {Calendar, LocaleConfig} from 'react-native-calendars';
const App = () => {
const [selected, setSelected] = useState('');
return (
<Calendar
onDayPress={day => {
setSelected(day.dateString);
}}
markedDates={{
[selected]: {selected: true, disableTouchEvent: true, selectedDotColor: 'orange'}
}}
/>
);
};
export default App;
1. Calendar 컴포넌트만 가져온 경우
다른 속성을 적용하지 않고 컴포넌트를 가져만 온 경우라면, 처음에는 모두 영어로 설정되어있기 때문에 한글로 설정을 바꿔주어야한다.
import { Calendar, LocaleConfig } from 'react-native-calendars';
<Calender />
한글 설정으로 바꾸기
import { Calendar, LocaleConfig } from 'react-native-calendars';
LocaleConfig.locales.fr = {
monthNames: [
'01월',
'02월',
'03월',
'04월',
'05월',
'06월',
'07월',
'08월',
'09월',
'10월',
'11월',
'12월',
],
monthNamesShort: [
'01월',
'02월',
'03월',
'04월',
'05월',
'06월',
'07월',
'08월',
'09월',
'10월',
'11월',
'12월',
],
dayNames: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
dayNamesShort: ['일', '월', '화', '수', '목', '금', '토'],
today: '오늘',
};
LocaleConfig.defaultLocale = 'fr';
2. 날짜 및 선택 관련 속성
- onDayPress : 달력의 특정 날짜를 클릭했을 때 호출되는 함수
- day 객체에는 dateString (예: 2025-07-03), day, month, year, timestemp 등의 속성이 포함된다.
- `selected` 상태를 선언해서 선택되면 상태 값이 바뀌도록 하는 함수를 넣어준다.
- markedDates : 달력의 틀정 날짜에 시각적인 마크를 추가하는 속성
- `[키]: { 값..... }` : key는 날짜 문자열('YYYY'-'MM'-'DD')이고, 값은 해당 날짜에 적용될 스타일 객체이다.
- `selected: true` : 해당 날짜를 선택된 상태로 표시한다. 일반적으로 배경색을 변경하기 위해 사용된다.
- `marked` : 점을 표시한다.
- `disabled` : 날짜를 비활성화한다.
- `disableTouchEvent: true` : 선택된 날짜를 다시 클릭해도 클릭이 되지 않는다 (처음 이후에는 클릭 이벤트 해제)
- `selectedColor` : 어떤 날짜를 클릭하면 그 날짜의 배경 색을 지정한다.
- `selectedTextColor` : `selected`가 true일 때 날짜 텍스트 색상을 지정한다.
- `dotColor` : 만약 어떤 지정 날짜에 점 표시(`mark`)가 있다면, 그 점의 색상(`dotColor`)을 바꾼다.
import React, { useState } from 'react';
import { Calendar } from 'react-native-calendars';
// 컴포넌트 내부
const [selected, setSelected] = useState('');
<Calendar
onDayPress={day => {
setSelected(day.dateString);
}}
markedDates={{
[selected]: {
selected: true,
disableTouchEvent: true,
selectedColor: 'blue'
},
'2025-07-09': { marked: true, dotColor: 'red' },
'2025-07-10': { marked: true, dotColor: 'green' },
}}
/>
3. UI 및 스타일 관련 속성
- `theme` : 캘린터의 전체적인 스타일을 커스터마이징하는 데 사용된다. 객체 형태로, 폰트, 색상, 패딩 등 다양한 스타일 속성을 지정할 수 있다.
사용 예시
// 예시
<Calendar
theme={{
backgroundColor: '#ffffff',
calendarBackground: '#ffffff',
textSectionTitleColor: '#b6c1cd',
selectedDayBackgroundColor: '#00adf5',
selectedDayTextColor: '#ffffff',
todayTextColor: '#00adf5',
// ...
}}
onDayPress={(day) => {
console.log('day pressed', day);
}}
/>
여러가지 theme 속성
theme={{
backgroundColor: '#ffffff',
calendarBackground: '#ffffff',
textSectionTitleColor: '#b6c1cd',
selectedDayBackgroundColor: '#00adf5',
selectedDayTextColor: '#ffffff',
todayTextColor: '#00adf5',
dayTextColor: '#2d4150',
textDisabledColor: '#d9e1e8',
dotColor: '#00adf5',
selectedDotColor: '#ffffff',
arrowColor: 'orange',
monthTextColor: 'blue',
indicatorColor: 'blue',
textDayFontFamily: 'monospace',
textMonthFontFamily: 'monospace',
textDayHeaderFontFamily: 'monospace',
textDayFontWeight: '300',
textMonthFontWeight: 'bold',
textDayHeaderFontWeight: '300',
textDayFontSize: 16,
textMonthFontSize: 16,
textDayHeaderFontSize: 16
}}
그외 속성
- `firstDay` : 주를 시작하는 요일을 설정한다. 0은 일요일, 1은 월요일이다.
- `showWeekNumbers` : 주(week) 번호를 표시할지 여부를 결정한다.
- `hideExtraDays` : 이전/다음 달의 날짜를 표시할지 여부를 결정한다. true로 설정하면 현재 월의 날짜만 표시된다.
- `disableArrowLeft` : 왼쪽 화살표를 비활성화할지 여부를 결정한다.
- `disableArrowRight` : 오른쪽 화살표를 비활성화할지 여부를 결정한다.
- `enableSwipeMonths` : 월을 스와이프하여 변경할 수 있도록 할지 여부를 결정한다.
- `hideDayNames` : 요일 이름(월, 화, 수 등)을 숨길지 여부를 결정한다.
- `displayLoadingIndicator` : 달력이 로드될 때 로딩 인디케이터를 표시할지 여부를 결정한다.
- `renderArrow` : 기본 화살표 아이콘 대신 사용자 정의 화살표를 렌더링할 수 있는 함수이다.
- `monthFormat` : 기본적으로는 "07월 2025년"처럼 되어있지만, 이 값을 `yyyy년 MM월`로 설정하면 년도가 먼저, 그리고 달이 다음에 나온다.
4. 스크롤 및 기타 속성
스크롤 속성들은 <CalendarList/> 컴포넌트에서만 사용할 수 있다.
CalenderLsit는 내부적으로 FlatList를 사용해서 성능적으로 큰 달력 리스트에 적합하다.
- `pastScrollRange` : 현재 날짜를 기준으로 과거 몇 개월까지 스크롤할 수 있는지 설정한다.
- `futureScrollRange` : 현재 날짜를 기준으로 미래 몇 개월까지 스크롤할 수 있는지 설정한다.
- `scrollEnabled` : 캘린더 스크롤을 활성화/비활성화한다.
- `pagingEnabled` : 월 단위로 끊어서 스크롤을 할지 여부를 설정한다.
- `horizontal` : 캘린더를 수평으로 스크롤할 수 있도록 한다.
- `showScrollIndicator` : 스크롤 인디케이터 표시 (Android에서만 효과 있음)
dayComponent 프로퍼티
dayComponent는 달력 안의 days에 관련된 것을 직접 커스텀할 수 있는 속성이다.
days에 관련된 것들은 모두 여기서 따로 저장시켜서 기능을 직접 구현해야하기 때문에, `onDayPress`와 `markedDate`가 불필요하다.
어떤 날짜를 누르면 그 날짜에 마크를 하고 오늘의 날짜 표시를 하는 등 커스텀을 직접 해보자.
1. 오늘의 날짜 표시
2. 선택된 날짜 표시
3. 일요일과 토요일 날짜 색상 표시
1. CalendarBox.tsx
Calendar 컴포넌트를 감싸는 View 컴포넌트이다.
Calendar 컴포넌트에 dayComponent 속성을 두고 이 속성 안에서 어떤 View를 보여주게 한다. 여기서 View 역할은 그 하위 컴포넌트가 터치 이벤트를 받을 수 있도록 TouchableOpacity로 한다.
onPress로 특정 날짜를 누르면 선택된 날짜인 selected가 그 날짜 string(예: '2025-07-14')로 저장된다.
TouchableOpacity 아래에는 days 관련 스타일을 지정하는 CalendarDayColor 라는 컴포넌트를 둔다.
import CalendarDayColor from './CalendarDayColor';
const CalendarBox = () => {
const [selected, setSelected] = useState('');
return (
<View>
<Calendar
dayComponent={({ date }) => {
<TouchableOpacity
onPress={() => {
setSelected(date.dateString);
}}>
<CalendarDayColor date={date} selected={selected === date.dateString} />
</TouchableOpacity>
}}
/>
);
};
2. CalendarDayColor.tsx
일요일과 토요일에 해당하는 days(날짜)를 각각 빨간색, 파란색으로 적용시키는 days 컴포넌트를 만들어보자.
2-1. 함수
- getDayColor() : 요일별 글씨 색상을 지정하는 함수이다.
- getDay()는 Date 객체의 메서드로, 요일을 숫자로 반환한다. 0이면 일요일, 1이면 월요일, ... , 6이면 토요일이다.
- 일요일(0)이면 빨간색, 토요일(6)이면 파란색, 그외에는 검정색으로 변경해준다.
- `todoDate()` : 오늘의 날짜를 'YYYY-MM-DD' 형식으로 바꾸는 함수로, 오늘의 날짜에 해당하는 숫자에 스타일을 주기 위해 쓰인다. Calendar에서 selected에는 정해진 형식의 날짜 문자열 값이 들어가야하기 때문에, `new Date()`로 오늘의 날짜를 구하고, 그 날짜를 포맷팅한다.
// 요일별 글씨 색상 지정
const getDayColor = (dateString: string) => {
const dayIndex = new Date(dateString).getDay();
if (dayIndex === 0) return '#BD2C0F'; // red
if (dayIndex === 6) return '#096AB3'; // blue
return '#1E2124';
};
// 오늘의 날짜를 'YYYY-MM-DD' 형식으로 구하기
// days 중에서 오늘의 날짜와 같으면 글자 색상 부여
const todayDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const formatDate = `${year}-${month}-${day}`;
return formatDate;
};
2-2. 함수 적용해서 반영하기
상위 컴포넌트에서 할당받은 date와 selected를 가지고 CalendarDayColor 컴포넌트에서 글씨 색상을 바꾸어준다.
1) 오늘의 날짜 표시하기: isToday - 회색 배경, 검정색 글씨
오늘의 날씨가 맞으면(`date.dateString === todayDate()`), 회색배경을 적용시킨다.
2) 선택된 날짜 표시하기: selected - 민트색 배경, 흰색 글씨
선택된 값이 존재하면(`selected && ..`), styles.selected 스타일을 적용시킨다.
3) 일요일, 토요일, 그외 요일 색상 표시하기: dayColor
오늘이 아니면 글씨는 dayColor(빨강, 파랑, 검정 중 하나)가 적용된다. 오늘이면 흰색 글씨가 적용된다.
오늘이 아니면 배경은 투명색이 적용된다. 오늘이면 회색 배경이 적용된다.
interface DateProps {
date?: {
dateString: string;
day: number;
};
selected: boolean;
}
const CalendarDayColor = ({ date, selected }: DateProps) => {
// days 관련 커스텀 적용
if (!date) return null;
const dayColor = getDayColor(date.dateString);
const isToday = date.dateString === todayDate();
return (
<View {/* 배경색 */}
style={[
{ backgroundColor: isToday ? '#F4F5F6' : 'transparent' },
selected && styles.selected,
]}
>
<Text {/* 글씨색 */}
style={[
{ color: isToday ? '#131416' : dayColor }, // 회색 : 흰색
selected && styles.selectedText,
]}
>
{date.day}
</Text>
</View>
);
};
MonthPicker
MonthPicker 는 년도와 월을 선택할 수 있는 picker이다.
https://www.npmjs.com/package/react-native-month-year-picker
react-native-month-year-picker
React Native Month Picker component for iOS & Android. Latest version: 1.9.0, last published: 3 years ago. Start using react-native-month-year-picker in your project by running `npm i react-native-month-year-picker`. There are 10 other projects in the npm
www.npmjs.com
전에는 라이브러리에서 기본으로 제공되는 Month 헤더로 이전 달과 다음 달을 조절했지만,
이번엔 picker을 통해서 원하는 달로 바로 이동해보자.
설치하기
npm install react-native-month-year-picker
cd ios && pod install
사용하기
- `date`상태 : 선택된 날짜를 Date 객체 형태로 저장한다.
- `show`상태 : picker를 보여주는지 여부를 나타낸다.
- `onValueChange` 함수 : `date`상태와 `show`상태를 조작하는 함수로, picker에서 날짜가 선택되면 `date`상태에 저장하고, 그 후에 닫아버리는 역할을 한다. MonthPicker 컴포넌트의 `onChange`속성에 들어간다.
import MonthPicker from 'react-native-month-year-picker';
const App = () => {
const [date, setDate] = useState(new Date()); // Date 객체로 저장한다.
const [show, setShow] = useState(false); // picker를 보여주는지 여부
// picker를 통해 선택된 'newDate'라는 매개변수에 Date 객체 형태로 들어온다.
// 선택되면 date가 갱신되고, picker을 닫아버린다.
const onValueChange = (event, newDate) => {
if (event === 'dateSetAction' && newDate) {
setDate(newDate);
}
setShow(false);
};
return (
<View>
<Text>Month Year Picker Example</Text>
<Text>
선택된 날짜: {date.getFullYear()}년 {date.getMonth() + 1}월
</Text>
{/* '월 선택하기'를 누르면 picker가 보여진다. */}
<TouchableOpacity onPress={() => setShow(true)}>
<Text>월 선택하기</Text>
</TouchableOpacity>
{show && (
<MonthPicker
onChange={onValueChange}
value={date}
minimumDate={new Date(2020, 0)} // 2020년 1월부터 가능
maximumDate={new Date(2025, 11)} // 2025년 12월까지 가능
locale="ko"
/>
)}
</View>
);
};
export default App;
최소 날짜와 최대 날짜를 지정했는데, picker에서는 모든 날짜가 보이지만 가능한 범위를 넘어서 클릭하면 picker가 최소 또는 최대 날짜에 맞게 되돌려놓는다.
picker 위치 문제 (1)
바깥의 SafeArea 스타일 때문에 picker가 오른쪽으로 이동해 밀릴수도 있다. 이런 경우에는 `<View className="flex items-center">`으로 MonthPicker을 감싸는 View를 만들고 중앙정렬하면 된다.
picker 위치문제 (2) - 더 안전하게 !
picker만 존재하는 경우는 위의 해결책대로 할 수도 있다. 하지만 한 화면에 다른 요소도 많다면 picker가 선택 버튼 위에 뜨거나 더 위에 뜨면서 아래 왼쪽 사진과 같이 SafeArea를 침범하는 경우가 생긴다. App에서 SafeArea로 감쌌지만 소용없었다.
이런 경우에는 <MonthPicker> 컴포넌트를 Modal 컴포넌트로 감싸서 항상 안전하게 화면 하단에서 나오게 하면 된다.
import { Modal } from 'react-native';
<Modal transparent visible={showPicker}>
<MonthPicker onChange={onChange} value={selectedDate} locale="ko" />
</Modal>
MonthPicker에서 설정한 달을 Calender 컴포넌트에 반영하기
전에 캘린더 라이브러리로 캘린d더를 만들었다.
MonthPicker에서 어떤 달을 선택하면, 캘린더에서 바로 그 선택한 달이 보이게 하려면 어떻게 해야할까?
MonthSelector.tsx
여기에서 `MonthPicker`을 구현하고, `Calendar`컴포넌트가 있는 곳(CalendarBox.tsx)에서 불러온다.
import { Modal } from 'react-native';
import MonthPicker from 'react-native-month-year-picker';
const MonthSelector = ({ selectedDate, setSelectedDate }: MonthSelectorProps) => {
const [showPicker, setShowPicker] = useState(false);
const onChange = (event: string, newDate?: Date) => {
if (event === 'dateSetAction' && newDate) {
const year = newDate.getFullYear();
const month = newDate.getMonth();
setSelectedDate(new Date(year, month, 1));
}
setShowPicker(false);
};
return (
<View>
{/* ▼ 상단 드롭다운 버튼 형태 */}
<TouchableOpacity
onPress={() => setShowPicker(true)}
>
<Text>{formatMonthText(selectedDate)}</Text> // `${year}년 ${month}월` 형식
<Text>🔽</Text>
</TouchableOpacity>
{/* ▼ Month Picker */}
{showPicker && (
<Modal transparent visible={showPicker}>
<MonthPicker onChange={onChange} value={selectedDate} locale="ko" />
</Modal>
)}
</View>
);
};
CalenderBox.tsx
이 공간에 MonthSelector.tsx 와 Calendar 컴포넌트가 함께 있어야한다.
- `selected` : Calendar 에서 `day` 선택 상태 ('YYYY-MM-DD' 문자열 타입)
- `selectedDate` : MonthPicker 에서 `date` 날짜 선택 상태 (Date 객체 타입)
- 원래는 이 상태가 MonthPicker가 있는 곳에서 필요하지만, 캘린더와 공유 속성을 가져야해서 CalendarBox에서 선언하고, MonthSelector.tsx에게는 프롭스로 넘겨주었다.
여기서 중요한 건 !!
Calendar의 `current`속성이다. 현재 선택된 달에 맞게 캘린더를 보여준다.
`current`에는 선택된 달의 `YYYY-MM-01` 형태의 날짜 문자열이 들어가야한다.
일반 Date 객체는 "2025-07-04T14:40:00.000Z" 이런 문자열이다. 그래서 그냥 Date 객체로 받는다면 UTC(협정 세계시) 문제로 8월을 선택하면 7월 31일로 들어가지게 되어서 항상 달의 첫 날이 되도록 설정했다.
그리고 picker에서 선택이 되어도 캘린더 컴포넌트는 이런 변경을 감지하지 못하는 경우가 있어, key로 완전히 새 렌더링이 되게 한다 `key`를 넣어서 picker에서 선택되면 캘린더도 바로 강제 리렌더링되게 하였다.
`key`는 `selectedDate`가 바뀔 때마다 바뀌기 때문이다.
import { Calendar } from 'react-native-calendars';
import MonthSelector from './MonthSelector';
const CalendarBox = () => {
const [selected, setSelected] = useState(''); // 캘린더의 days 선택 상태
const [selectedDate, setSelectedDate] = useState(new Date()); // MonthPicker의 date 선택 상태
return (
<View>
<MonthSelector selectedDate={selectedDate} setSelectedDate={setSelectedDate} />
<Calendar
key={formatDateToYYYYMMDD(selectedDate)} // 🔑 강제 리렌더링
current={formatDateToYYYYMMDD(selectedDate)} // YYYY-MM-01
{/* .... 등등 속성 */}
/>
</View>
);
};
export default CalendarBox;
이제 picker에서 선택을 완료하면 바로 캘린더에 선택된 달이 반영되어 보여진다.