Date Validation
- Always validate selected dates against your business logic
- Use
minDate
andmaxDate
props to restrict date selection - Provide clear visual feedback for unavailable dates
Calendar provides a feature-rich date selection interface with a custom header that includes month/year picker modal, marked dates support, and seamless theme integration. Built on top of react-native-calendars with enhanced navigation and visual customization.
import { Calendar } from 'rnc-theme';import type { CalendarProps, ListMarkedDates } from 'rnc-theme';
<Calendar onDayPress={(day) => { console.log('Selected day:', day); }}/>
const [selectedDate, setSelectedDate] = useState(null);
<Calendar selectedDate={selectedDate} onDayPress={(day) => { setSelectedDate(day); }}/>
const markedDates = [ { date: '2024-06-15', marked: true }, { date: '2024-06-20', marked: true }, { date: '2024-06-25', marked: true }];
<Calendar listMarkedDates={markedDates} onDayPress={(day) => { console.log('Selected:', day); }}/>
<Calendar current="2024-12-01" onMonthChange={(month) => { console.log('Month changed:', month); }}/>
Prop | Type | Default | Description |
---|---|---|---|
selectedDate | DateData | undefined | undefined | Currently selected date object |
listMarkedDates | ListMarkedDates | undefined | Array of dates to mark with dots |
current | string | Today’s date | Initial month to display (YYYY-MM-DD) |
onMonthChange | (month: DateData) => void | undefined | Callback when month changes |
onDayPress | (day: DateData) => void | undefined | Callback when day is pressed |
...props | RNCalendarProps | - | All react-native-calendars props |
type DatePattern = `${number}${number}${number}${number}-${number}${number}-${number}${number}`;
type MarkedDateItem = { date: DatePattern; marked: boolean;};
type ListMarkedDates = MarkedDateItem[];
interface CalendarProps extends RNCalendarProps { selectedDate?: DateData | undefined; listMarkedDates?: ListMarkedDates;}
interface DateData { dateString: string; // '2024-06-15' day: number; // 15 month: number; // 6 year: number; // 2024 timestamp: number; // Unix timestamp}
The calendar includes a custom header that displays the current month and selected date, with a dropdown button that opens a modal picker for easy month and year navigation.
<Calendar selectedDate={selectedDate} onDayPress={(day) => setSelectedDate(day)} onMonthChange={(month) => { console.log('Navigated to:', month.dateString); }}/>
Fixed navigation arrows positioned over the calendar header provide intuitive month navigation without interfering with the native calendar component.
The calendar automatically adapts to your app’s theme through the useTheme
hook, ensuring consistent styling across your application.
const EventCalendar = () => { const [selectedDate, setSelectedDate] = useState(null); const [events, setEvents] = useState([ { date: '2024-06-15', marked: true }, { date: '2024-06-18', marked: true }, { date: '2024-06-22', marked: true }, { date: '2024-06-28', marked: true } ]);
return ( <VStack spacing="lg" padding="md"> <Calendar selectedDate={selectedDate} listMarkedDates={events} onDayPress={(day) => { setSelectedDate(day); }} onMonthChange={(month) => { // Load events for the new month loadEventsForMonth(month.month, month.year); }} />
{selectedDate && ( <Card padding="md"> <Text>Selected: {selectedDate.dateString}</Text> </Card> )} </VStack> );};
const AppointmentBooking = () => { const [selectedDate, setSelectedDate] = useState(null); const [availableDates, setAvailableDates] = useState([ { date: '2024-06-17', marked: true }, { date: '2024-06-19', marked: true }, { date: '2024-06-21', marked: true }, { date: '2024-06-24', marked: true }, { date: '2024-06-26', marked: true } ]);
const isDateAvailable = (dateString) => { return availableDates.some(date => date.date === dateString); };
return ( <VStack spacing="lg" padding="lg"> <Text variant="heading2">Select Appointment Date</Text>
<Calendar selectedDate={selectedDate} listMarkedDates={availableDates} onDayPress={(day) => { if (isDateAvailable(day.dateString)) { setSelectedDate(day); } else { Alert.alert('Date Unavailable', 'Please select an available date marked with a dot.'); } }} minDate={new Date().toISOString().split('T')[0]} />
{selectedDate && ( <VStack spacing="md"> <Text>Selected Date: {selectedDate.dateString}</Text> <Button variant="primary" onPress={() => bookAppointment(selectedDate)} > <ButtonText>Book Appointment</ButtonText> </Button> </VStack> )} </VStack> );};
const DateRangePicker = () => { const [startDate, setStartDate] = useState(null); const [endDate, setEndDate] = useState(null); const [markedDates, setMarkedDates] = useState([]);
const generateDateRange = (start, end) => { const dates = []; const currentDate = new Date(start.timestamp); const endDateTime = new Date(end.timestamp);
while (currentDate <= endDateTime) { dates.push({ date: currentDate.toISOString().split('T')[0], marked: true }); currentDate.setDate(currentDate.getDate() + 1); } return dates; };
const handleDayPress = (day) => { if (!startDate || (startDate && endDate)) { // Start new selection setStartDate(day); setEndDate(null); setMarkedDates([{ date: day.dateString, marked: true }]); } else if (startDate && !endDate) { // Complete the range if (day.timestamp >= startDate.timestamp) { setEndDate(day); setMarkedDates(generateDateRange(startDate, day)); } else { // Selected date is before start date, restart setStartDate(day); setMarkedDates([{ date: day.dateString, marked: true }]); } } };
return ( <VStack spacing="lg" padding="lg"> <Text variant="heading2">Select Date Range</Text>
<Calendar listMarkedDates={markedDates} onDayPress={handleDayPress} markingType="period" />
<VStack spacing="sm"> <Text>Start Date: {startDate?.dateString || 'Not selected'}</Text> <Text>End Date: {endDate?.dateString || 'Not selected'}</Text>
{startDate && endDate && ( <Button variant="primary" onPress={() => handleDateRangeSelection(startDate, endDate)} > <ButtonText>Confirm Selection</ButtonText> </Button> )} </VStack> </VStack> );};
const HolidayCalendar = () => { const [holidays, setHolidays] = useState([ { date: '2024-06-12', marked: true }, // Independence Day { date: '2024-06-17', marked: true }, // Pancasila Day { date: '2024-06-29', marked: true }, // Eid al-Adha ]);
const [selectedDate, setSelectedDate] = useState(null);
const getHolidayInfo = (dateString) => { const holidayMap = { '2024-06-12': 'Independence Day', '2024-06-17': 'Pancasila Day', '2024-06-29': 'Eid al-Adha' }; return holidayMap[dateString]; };
return ( <VStack spacing="lg" padding="lg"> <Text variant="heading2">Holiday Calendar 2024</Text>
<Calendar current="2024-06-01" selectedDate={selectedDate} listMarkedDates={holidays} onDayPress={(day) => { setSelectedDate(day); }} />
{selectedDate && ( <Card padding="md"> <VStack spacing="sm"> <Text variant="subheading"> {selectedDate.dateString} </Text> {getHolidayInfo(selectedDate.dateString) ? ( <Text variant="body" color="primary"> 🎉 {getHolidayInfo(selectedDate.dateString)} </Text> ) : ( <Text variant="body" color="textSecondary"> Regular day </Text> )} </VStack> </Card> )} </VStack> );};
const CustomThemedCalendar = () => { const { theme } = useTheme();
return ( <Calendar selectedDate={selectedDate} onDayPress={handleDayPress} // Additional theme customization theme={{ textDayFontSize: theme.fontSizes.lg, selectedDayBackgroundColor: theme.colors.secondary, todayTextColor: theme.colors.warning, }} /> );};
const DynamicMarkedDates = () => { const [markedDates, setMarkedDates] = useState([]); const [loading, setLoading] = useState(false);
const fetchMarkedDatesForMonth = async (month, year) => { setLoading(true); try { const response = await api.getMarkedDates(month, year); const formatted = response.map(date => ({ date: date.dateString, marked: true })); setMarkedDates(formatted); } catch (error) { console.error('Failed to fetch marked dates:', error); } finally { setLoading(false); } };
return ( <VStack spacing="md"> {loading && <LoadingSpinner />}
<Calendar listMarkedDates={markedDates} onMonthChange={(month) => { fetchMarkedDatesForMonth(month.month, month.year); }} onDayPress={(day) => { console.log('Selected:', day); }} /> </VStack> );};
The calendar automatically inherits theme properties but can be customized further:
const calendarTheme = { // Background colors calendarBackground: theme.colors.surface,
// Text colors dayTextColor: theme.colors.text, textSectionTitleColor: theme.colors.textSecondary, todayTextColor: theme.colors.primary, selectedDayTextColor: theme.colors.surface,
// Selection colors selectedDayBackgroundColor: theme.colors.primary, todayBackgroundColor: theme.colors.primary + '15',
// Dots for marked dates dotColor: theme.colors.primary, selectedDotColor: theme.colors.surface,
// Navigation arrowColor: theme.colors.primary, disabledArrowColor: theme.colors.muted,};
The custom header is fully integrated with your theme and includes:
Date Validation
minDate
and maxDate
props to restrict date selectionPerformance
React.memo
for calendar components that re-render frequentlyUser Experience