Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Calendar App in React Native

Calendar App

~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

A hand-built month calendar bundles many topics from this tutorial: date arithmetic in plain JavaScript, a grid layout with FlatList/numColumns, selection state, and a per-day event list.

1. Description

React Native has NO built-in calendar component. A month grid is computed yourself: the month's first weekday determines how many empty cells sit at the start, new Date(year, month + 1, 0).getDate() gives the number of days in the month.

2. Short example

const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstWeekday = new Date(year, month, 1).getDay(); // 0 = Sunday

3. Complete project: a month calendar with events

npx create-expo-app calendar-app
cd calendar-app
utils/calendar.js
const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
const MONTH_NAMES = [
  'January', 'February', 'March', 'April', 'May', 'June',
  'July', 'August', 'September', 'October', 'November', 'December',
];

// Produces a grid of day numbers (null = empty filler cell before the 1st)
export function buildMonthGrid(year, month) {
  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const firstWeekday = new Date(year, month, 1).getDay();

  const cells = [];
  for (let i = 0; i < firstWeekday; i++) {
    cells.push(null);
  }
  for (let day = 1; day <= daysInMonth; day++) {
    cells.push(day);
  }
  return cells;
}

export function keyFor(year, month, day) {
  return `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
}

export { WEEKDAYS, MONTH_NAMES };
App.js
import { useState, useMemo } from 'react';
import { View, Text, TouchableOpacity, FlatList, StyleSheet } from 'react-native';
import { buildMonthGrid, keyFor, WEEKDAYS, MONTH_NAMES } from './utils/calendar';

const EVENTS = {
  [keyFor(new Date().getFullYear(), new Date().getMonth(), 5)]: ['Dentist 10:00'],
  [keyFor(new Date().getFullYear(), new Date().getMonth(), 14)]: ['Team meeting 14:00', "Anna's birthday"],
};

export default function App() {
  const today = new Date();
  const [year, setYear] = useState(today.getFullYear());
  const [month, setMonth] = useState(today.getMonth());
  const [selectedDay, setSelectedDay] = useState(today.getDate());

  const cells = useMemo(() => buildMonthGrid(year, month), [year, month]);

  function changeMonth(delta) {
    const newDate = new Date(year, month + delta, 1);
    setYear(newDate.getFullYear());
    setMonth(newDate.getMonth());
    setSelectedDay(null);
  }

  const selectedKey = selectedDay
    ? keyFor(year, month, selectedDay)
    : null;
  const eventsOnDay = EVENTS[selectedKey] ?? [];

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <TouchableOpacity onPress={() => changeMonth(-1)}>
          <Text style={styles.arrow}>‹</Text>
        </TouchableOpacity>
        <Text style={styles.monthTitle}>{MONTH_NAMES[month]} {year}</Text>
        <TouchableOpacity onPress={() => changeMonth(1)}>
          <Text style={styles.arrow}>›</Text>
        </TouchableOpacity>
      </View>

      <View style={styles.weekdayRow}>
        {WEEKDAYS.map((w) => (
          <Text key={w} style={styles.weekday}>{w}</Text>
        ))}
      </View>

      <FlatList
        data={cells}
        numColumns={7}
        keyExtractor={(_, index) => String(index)}
        renderItem={({ item: day }) => {
          if (day === null) return <View style={styles.cell} />;
          const hasEvent = Boolean(EVENTS[keyFor(year, month, day)]);
          const isSelected = day === selectedDay;
          return (
            <TouchableOpacity
              style={styles.cell}
              onPress={() => setSelectedDay(day)}
            >
              <View style={[styles.dayCircle, isSelected && styles.dayCircleActive]}>
                <Text style={[styles.dayText, isSelected && styles.dayTextActive]}>
                  {day}
                </Text>
              </View>
              {hasEvent && <View style={styles.eventDot} />}
            </TouchableOpacity>
          );
        }}
      />

      <View style={styles.eventsSection}>
        <Text style={styles.eventsTitle}>
          Events {selectedDay ? `on the ${selectedDay}.` : ''}
        </Text>
        {eventsOnDay.length === 0 && (
          <Text style={styles.noEvents}>No events.</Text>
        )}
        {eventsOnDay.map((event) => (
          <Text key={event} style={styles.event}>• {event}</Text>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, paddingTop: 60, paddingHorizontal: 16 },
  header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 },
  arrow: { fontSize: 24, paddingHorizontal: 16, color: '#2563eb' },
  monthTitle: { fontSize: 18, fontWeight: 'bold' },
  weekdayRow: { flexDirection: 'row', marginBottom: 4 },
  weekday: { flex: 1, textAlign: 'center', color: '#9ca3af', fontSize: 12 },
  cell: { flex: 1, aspectRatio: 1, alignItems: 'center', justifyContent: 'center' },
  dayCircle: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center' },
  dayCircleActive: { backgroundColor: '#2563eb' },
  dayText: { fontSize: 14 },
  dayTextActive: { color: 'white', fontWeight: 'bold' },
  eventDot: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: '#f59e0b', marginTop: 2 },
  eventsSection: { marginTop: 20, borderTopWidth: 1, borderTopColor: '#e5e7eb', paddingTop: 12 },
  eventsTitle: { fontWeight: 'bold', marginBottom: 8 },
  noEvents: { color: '#9ca3af' },
  event: { marginBottom: 4 },
});

4. Explanation

  • buildMonthGrid() is PURE calculation logic with no React dependency – it could be used just the same in a Node.js script or a unit test, a deliberately clean, separated building block.
  • useMemo(() => ..., [year, month]) prevents the grid from being recomputed on EVERY render (e.g. when selecting a day), only when the year or month actually changes.
  • numColumns={{7}} on FlatList produces the weekly grid – null entries at the start of the month render as empty, but equally sized, Views, so the grid doesn't visually shift.
  • EVENTS is an object whose keys are 'YYYY-MM-DD' strings (keyFor()) – a common pattern for O(1) date-based lookups without an expensive array search.

5. Outputs

Ausgabe
A header with "‹ [current month] [year] ›", below it weekday abbreviations (Su–Sa), below that a 7-column number grid. Days with events show a small orange dot below them; the selected day is highlighted in blue. Below that, an "Events on the [day]." list, e.g. "• Team meeting 14:00" and "• Anna's birthday" for the 14th.