React Native Calendar Agenda: Host-Supplied Events
By Native Animation · Updated
A React Native calendar agenda displays events supplied by your app and lets people move between a day's schedule and a calendar view. Keep event identity, fetching, time-zone conversion, and persistence in the host. The example below passes tasks and month schedules into an animated planner; it does not read or create events in the device's system calendar.
Choose an agenda, date picker, or system calendar
| Need | Starting point | Host responsibility |
|---|---|---|
| Choose a date for a form | A date picker | Store the accepted date and validate its range |
| Browse your app's schedule | An agenda with supplied events | Fetch, format, and refresh event records |
| Animate Today into a compact schedule | Today Agenda Morph | Supply matching task/day data and handle add requests |
| Read or write device calendar records | Expo Calendar for SDK 58 | Integrate the separate platform API, permissions, and error states |
A visual calendar does not imply device-calendar access. This guide needs no calendar permission. If you later connect Expo Calendar, use its SDK 58 documentation rather than assuming an older API example still applies. A segmented control can switch views in a simpler agenda that does not need shared task surfaces.
Event identity and time zones
Store a stable event ID and canonical start/end instants in your data layer. Group and format those records for the viewer's chosen time zone before passing display strings to the component. An event near midnight may belong to different dates in different zones; splitting an ISO string at the letter T is not a time-zone conversion. Keep all-day date ranges separate from timed instants, and resolve daylight-saving changes in the scheduling layer.
Today Agenda Morph consumes caller-formatted labels, not a recurrence engine or timestamp database. Its two time-zone labels are also supplied strings; they do not run live clocks or convert event times.
Supply real tasks and month schedules
After copying the folder with the CLI, install SDK-compatible dependencies:
npx expo install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-workletsUse bunx instead of npx in Bun projects. This integration targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, Worklets 0.13.0, and Skia 2.14.0. Follow the SDK 58 setup for Gesture Handler, Reanimated, and Skia. Wrap the screen in GestureHandlerRootView. On web, initialize CanvasKit through Skia's deferred loading pattern before importing the piece.
Save this complete fixture outside your Expo Router route directory. Render it inside a bounded screen with safe-area padding supplied once by the host. October has one supplied event; November is deliberately empty. Other months have no loaded schedule. An add request only updates explanatory text here: connect it to your editor and persist the resulting record separately.
import { useRef, useState } from 'react';
import { Button, Text, View } from 'react-native';
import {
TodayAgendaMorph, type AgendaDate, type AgendaDay,
type AgendaTask, type TodayAgendaMorphRef,
} from './today-agenda-morph';
const date: AgendaDate = {
weekday: 'Tuesday', day: '13', month: 'OCT', prefix: '10.',
monthIndex: 9, year: 2026,
};
const tasks: AgendaTask[] = [{
id: 'review-1013', title: 'Project review', chipTitle: 'Review',
start: '10:00 AM', end: '11:00 AM', duration: '60 min', initials: ['AL'],
}];
const october: AgendaDay[] = [{
weekday: 'TUE', day: '13', month: 'OCT',
hours: [{ time: '10 AM', events: [{ title: 'Project review', taskId: 'review-1013' }] }],
}];
const schedules: Record<number, AgendaDay[]> = { 0: october, 1: [] };
export function AgendaExample() {
const planner = useRef<TodayAgendaMorphRef>(null);
const [offset, setOffset] = useState(0);
const [notice, setNotice] = useState('No editor request yet.');
const schedule = schedules[offset];
return (
<View style={{ flex: 1 }}>
<Button title="Show calendar" onPress={() => planner.current?.setMode('calendar')} />
<Text accessibilityLiveRegion="polite">
{schedule === undefined ? 'This month has not been loaded.'
: schedule.length === 0 ? 'No events in this month.' : 'Schedule loaded.'}
</Text>
<Text accessibilityLiveRegion="polite">{notice}</Text>
<TodayAgendaMorph ref={planner} date={date} tasks={tasks} days={october}
getMonthDays={(monthOffset) => schedules[monthOffset] ?? []}
onMonthChange={setOffset}
onAddEvent={(dayIndex, hourIndex) => {
const day = dayIndex === undefined ? undefined : schedule?.[dayIndex];
const slot = hourIndex === undefined ? undefined : day?.hours[hourIndex];
setNotice(day && slot
? `Editor requested for ${day.month} ${day.day}, ${slot.time}.`
: 'Editor requested without a preset time.');
}}
style={{ flex: 1 }} />
</View>
);
}Match shared surfaces
The date and first day row both describe October 13. The first event's taskId matches the task's id, and its hour occupies the same column as that task. Preserve this ordering when projecting your event records into the visual model: the morph shares surfaces by task identity and slot position, not by comparing formatted titles.
The component displays at most three morphing tasks, three hour columns per day, and the first event in each hour cell. Use a separate full event list or detail screen for overflow; do not silently present this compact preview as a complete agenda with unlimited overlapping appointments. It is not a full month-grid date picker.
Loading, empty months, and add requests
Provide getMonthDays even when you have only loaded one month. Without it, the component reuses days with different month labels, which is appropriate for a visual demo but can misrepresent a real schedule. Returning an empty array deliberately avoids repeating October's event under November. The host's status text distinguishes a loaded empty month from a month whose data is unavailable.
getMonthDays is synchronous and may be called for adjacent pages during rendering. Read a cached map there; do not start fetches inside it or return a Promise. Prefetch neighboring months in the host, cancel superseded requests, and update the map only when the response belongs to the requested month and time zone. Keep a separate loading/error state with retry; empty results must not stand in for a failed fetch.
Month offsets are relative to date and bounded to minus twelve through plus twelve. onMonthChange reports the settled offset; the ref's goToMonth(offset) requests that relative page. A jump across December needs the host's correct year and date records, not only a new month abbreviation.
Keep data separate from display strings
onAddEvent receives optional day and hour indexes, not timestamps or event IDs. Resolve those indexes against the displayed month's supplied data. The main add button supplies neither index. The fixture has immutable schedules; an asynchronous application should disable or defer mutations while a month is changing and resolve the final editor request against its accepted view. The callback requests an editor; it does not append a record, save to a server, or emit a device calendar event.
Keep saved events when changing presentation mode. Today is a task stack and Calendar is a compact schedule of those records, so changing mode should not recreate IDs or replace the underlying schedule. If you open an editor in a sheet, use the overlay guide for the presentation and focus boundary.
Motion, accessibility, and small screens
One shared progress value coordinates the oversized date, task surfaces, copy, and calendar rows. Tapping the view tabs provides the same operation as dragging; month chevrons provide alternatives to horizontal swipes. The piece uses interruptible springs with explicit mass, and Reduced Motion replaces the spatial flights with a staged fade rather than making every transition instant.
Tasks announce their complete titles and time ranges. Activating a task cycles the stack; there is no public onEventPress prop that automatically opens your event details. The add callback identifies a day/hour slot. Keep these meanings clear in your surrounding copy and test the final reading order with VoiceOver and TalkBack using the React Native accessibility guidance.
The board measures its host, caps its content width, and scrolls vertically in short containers. Keep a bounded height so that scroll can work, allow text scaling, and check the first and last day at large font sizes. The compact event chips truncate visual text while exposing the full title to screen readers; a complete event-details destination should preserve all of that information.
When to use Today Agenda Morph
Choose Today Agenda Morph when continuity between a daily task stack and a compact calendar is the product interaction you want. Its public API supplies tasks, days, date, month schedules, labels, palette, fonts, callbacks, and setMode/goToMonth ref methods. Choose a general calendar or agenda library when you need arbitrary overlapping events, recurrence editing, an unrestricted month grid, or a large virtualized event feed.
Each published piece is reviewed on physical iPhone and Android devices with Expo Go before release. Your host still owns data correctness, editor navigation, safe areas, and persistence. Browse micro-interactions for complementary controls.
FAQ
Does this React Native calendar agenda read system events?
No. The example passes host-supplied tasks and days as props. Device calendar access is a separate integration with Expo Calendar and its platform requirements.
Why does the same event appear in every month?
Without getMonthDays, the piece reuses the supplied day schedule while changing month labels. Supply an explicit schedule for each relative month and distinguish unloaded months from loaded empty ones.
Can I display every appointment in a busy day?
This component is a compact morphing view: it shows three task surfaces, three hour columns, and one event per cell. Add an explicit overflow destination or choose a full agenda list for dense schedules.
Does tapping Add save an event?
No. onAddEvent requests an editor using optional day/hour indexes. Your app validates and persists the event, then supplies updated data to the agenda.