◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Getting Started
◈ Getting Started/2026-03-24Beginner

Build a Calendar & Schedule App with Rork — A Complete Beginner's Tutorial

Learn how to build a fully functional calendar and schedule management app from scratch using Rork. This step-by-step tutorial covers calendar UI, event CRUD, local storage, and reminder notifications.

Rork585Calendar AppSchedule ManagementTutorial5Beginner7React Native239Expo219

Setup and context — Learn by Building a Calendar App

Schedule management apps are among the most universally useful types of mobile applications. By building one, you'll naturally learn key app development concepts like state management, data persistence, UI component design, and user interaction patterns — all in a single project.

Below, we build a calendar and schedule management app from scratch using Rork. Even with minimal coding experience, you can lean on Rork's AI assistant to turn well-crafted prompts into production-ready code, freeing you to focus on design and functionality decisions.

What you'll learn:

  • Building a month-view calendar UI with event markers
  • Managing event data (create, read, update, delete)
  • Persisting data locally with AsyncStorage
  • Adding reminder notifications with Expo Notifications
  • Polishing the UI to App Store / Google Play standards

If you're brand new to Rork, we recommend starting with "Getting Started with Rork" to get familiar with the basics.


Prerequisites & Setup

What You'll Need

  • A Rork account (the free plan covers everything in this tutorial)
  • A smartphone (iOS or Android — for previewing with Expo Go)
  • A web browser on your computer (to access the Rork editor)

Helpful Background Knowledge

  • Basic understanding of HTML/CSS
  • Familiarity with JavaScript variables, functions, and arrays

That said, Rork's AI handles the heavy lifting, so you can follow along even without deep programming knowledge. Whenever something is unclear, just ask Rork's chat — it'll explain any code it generates.


Step 1: Create Your Project

Log into the Rork dashboard and click "New Project." Name it something descriptive like my-calendar-app.

For your initial prompt, try something like this:

Build a calendar and schedule management app with these features:
1. Month-view calendar UI (highlight today's date)
2. Tap a date to see that day's events
3. Event creation form (title, start time, end time, notes)
4. Edit and delete events
5. Save data locally with AsyncStorage
6. Modern, clean design with a blue color theme

Rork will generate the screen layout, components, and navigation structure based on this prompt. After a few minutes, you'll see a preview — scan the QR code with Expo Go to test it on your device.


Step 2: Build the Calendar UI

Rork typically generates calendar components using the react-native-calendars library. Here's what the core calendar component looks like with event dot markers:

// CalendarScreen.tsx — Main calendar screen
import React, { useState, useMemo } from 'react';
import { View, StyleSheet } from 'react-native';
import { Calendar } from 'react-native-calendars';
 
type Event = {
  id: string;
  title: string;
  date: string; // 'YYYY-MM-DD'
  startTime: string;
  endTime: string;
  memo: string;
};
 
type Props = {
  events: Event[];
  onDayPress: (dateString: string) => void;
};
 
export default function CalendarScreen({ events, onDayPress }: Props) {
  const [selectedDate, setSelectedDate] = useState('');
 
  // Add dot markers to dates that have events
  const markedDates = useMemo(() => {
    const marks: Record<string, any> = {};
    events.forEach((event) => {
      marks[event.date] = {
        ...marks[event.date],
        marked: true,
        dotColor: '#4A90D9',
      };
    });
    if (selectedDate) {
      marks[selectedDate] = {
        ...marks[selectedDate],
        selected: true,
        selectedColor: '#4A90D9',
      };
    }
    return marks;
  }, [events, selectedDate]);
 
  const handleDayPress = (day: { dateString: string }) => {
    setSelectedDate(day.dateString);
    onDayPress(day.dateString);
  };
 
  return (
    <View style={styles.container}>
      <Calendar
        markedDates={markedDates}
        onDayPress={handleDayPress}
        theme={{
          todayTextColor: '#4A90D9',
          arrowColor: '#4A90D9',
          monthTextColor: '#333',
          textDayFontSize: 16,
          textMonthFontSize: 18,
          textDayHeaderFontSize: 14,
        }}
      />
    </View>
  );
}
 
// Expected output:
// A month-view calendar is displayed with blue dots on dates that have events.
// Tapping a date highlights it (blue background) and shows that day's events below.
 
const styles = StyleSheet.create({
  container: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    overflow: 'hidden',
    margin: 16,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
});

The key detail here is using useMemo to efficiently compute markedDates. Whenever the event data changes, the calendar markers update automatically without unnecessary re-renders.


Step 3: Build the Event Creation Form

Next, let's implement the form for adding events. Give Rork a prompt like this:

Create an event form with:
- Title (required, text input)
- Date (selected via DateTimePicker)
- Start time and end time (TimePicker)
- Notes (optional, multiline text)
- Save and Cancel buttons
- Validation: show error if title is empty

Here's what the core form component typically looks like:

// AddEventForm.tsx — Event creation form (core logic)
import React, { useState } from 'react';
import {
  View, Text, TextInput, TouchableOpacity,
  Alert, StyleSheet, ScrollView,
} from 'react-native';
 
type EventInput = {
  title: string;
  date: string;
  startTime: string;
  endTime: string;
  memo: string;
};
 
type Props = {
  initialDate: string;
  onSave: (event: EventInput) => void;
  onCancel: () => void;
};
 
export default function AddEventForm({ initialDate, onSave, onCancel }: Props) {
  const [title, setTitle] = useState('');
  const [memo, setMemo] = useState('');
  const [startTime, setStartTime] = useState('09:00');
  const [endTime, setEndTime] = useState('10:00');
 
  const handleSave = () => {
    if (!title.trim()) {
      Alert.alert('Validation Error', 'Please enter a title for this event');
      return;
    }
    onSave({
      title: title.trim(),
      date: initialDate,
      startTime,
      endTime,
      memo: memo.trim(),
    });
  };
 
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.label}>Title *</Text>
      <TextInput
        style={styles.input}
        value={title}
        onChangeText={setTitle}
        placeholder="Meeting, lunch, study group..."
      />
 
      <Text style={styles.label}>Start Time</Text>
      <TextInput
        style={styles.input}
        value={startTime}
        onChangeText={setStartTime}
        placeholder="09:00"
      />
 
      <Text style={styles.label}>End Time</Text>
      <TextInput
        style={styles.input}
        value={endTime}
        onChangeText={setEndTime}
        placeholder="10:00"
      />
 
      <Text style={styles.label}>Notes</Text>
      <TextInput
        style={[styles.input, styles.memoInput]}
        value={memo}
        onChangeText={setMemo}
        placeholder="Additional details (optional)"
        multiline
        numberOfLines={4}
      />
 
      <View style={styles.buttonRow}>
        <TouchableOpacity style={styles.cancelButton} onPress={onCancel}>
          <Text style={styles.cancelText}>Cancel</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.saveButton} onPress={handleSave}>
          <Text style={styles.saveText}>Save</Text>
        </TouchableOpacity>
      </View>
    </ScrollView>
  );
}
 
// Expected output:
// A clean form with fields for title, times, and notes.
// Tapping "Save" validates the input and saves the event.
// If the title is empty, an alert reads "Please enter a title for this event."
 
const styles = StyleSheet.create({
  container: { padding: 20 },
  label: { fontSize: 14, fontWeight: '600', color: '#555', marginTop: 16, marginBottom: 6 },
  input: {
    borderWidth: 1, borderColor: '#DDD', borderRadius: 8,
    padding: 12, fontSize: 16, backgroundColor: '#FAFAFA',
  },
  memoInput: { height: 100, textAlignVertical: 'top' },
  buttonRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 24 },
  cancelButton: {
    flex: 1, marginRight: 8, padding: 14, borderRadius: 8,
    borderWidth: 1, borderColor: '#DDD', alignItems: 'center',
  },
  cancelText: { fontSize: 16, color: '#666' },
  saveButton: {
    flex: 1, marginLeft: 8, padding: 14, borderRadius: 8,
    backgroundColor: '#4A90D9', alignItems: 'center',
  },
  saveText: { fontSize: 16, color: '#FFF', fontWeight: '600' },
});

Step 4: Persist Data with AsyncStorage

To make sure events survive app restarts, we'll use AsyncStorage for local persistence.

// storage.ts — Data persistence utilities
import AsyncStorage from '@react-native-async-storage/async-storage';
 
const EVENTS_KEY = '@calendar_events';
 
export type CalendarEvent = {
  id: string;
  title: string;
  date: string;
  startTime: string;
  endTime: string;
  memo: string;
};
 
// Load all events
export async function loadEvents(): Promise<CalendarEvent[]> {
  try {
    const json = await AsyncStorage.getItem(EVENTS_KEY);
    return json ? JSON.parse(json) : [];
  } catch (error) {
    console.error('Failed to load events:', error);
    return [];
  }
}
 
// Save all events
export async function saveEvents(events: CalendarEvent[]): Promise<void> {
  try {
    await AsyncStorage.setItem(EVENTS_KEY, JSON.stringify(events));
  } catch (error) {
    console.error('Failed to save events:', error);
  }
}
 
// Add a single event
export async function addEvent(
  event: Omit<CalendarEvent, 'id'>
): Promise<CalendarEvent> {
  const events = await loadEvents();
  const newEvent: CalendarEvent = {
    ...event,
    id: Date.now().toString(),
  };
  events.push(newEvent);
  await saveEvents(events);
  return newEvent;
}
 
// Delete an event
export async function deleteEvent(id: string): Promise<void> {
  const events = await loadEvents();
  const filtered = events.filter((e) => e.id !== id);
  await saveEvents(filtered);
}
 
// Expected output:
// loadEvents() → Returns the saved events array (empty array on first call)
// addEvent({...}) → Assigns a new ID, saves, and returns the created event
// deleteEvent('123') → Removes the event with that ID and saves the updated list

This pattern gives you a solid local storage foundation. When you're ready to scale up, you can swap AsyncStorage for Supabase or Firebase to enable cloud syncing across devices.


Step 5: Add Reminder Notifications

Let your users receive a reminder before their events by adding local push notifications. Tell Rork:

Add a feature that sends a local notification reminder 15 minutes before each event.
Use Expo Notifications and schedule it automatically when an event is saved.

For a deeper dive into push notifications, check out "Implementing Push Notifications in Rork."


Common Errors and Fixes

Calendar Doesn't Render

This usually means react-native-calendars wasn't installed properly. Run npx expo install react-native-calendars in the Rork terminal to fix it.

Events Disappear After Restarting the App

Double-check that AsyncStorage is correctly imported from @react-native-async-storage/async-storage. Verify the package is listed in your package.json.

DateTimePicker Looks Different on iOS vs. Android

This is expected — @react-native-community/datetimepicker uses each platform's native picker UI. If you want a consistent look across platforms, ask Rork to build a custom modal picker instead.


Taking It Further

Once you have the basics working, consider adding these features to make your app stand out:

Week and day views: Beyond the month view, adding a weekly timeline or daily hour-by-hour view makes the app feel much more professional and practical for power users.

Category tagging with color coding: Let users tag events as "Work," "Personal," "Health," etc., with distinct colors. This makes the calendar far more scannable at a glance.

Recurring events: Automatically generating weekly or monthly recurring events is a huge quality-of-life feature that users love.

Google Calendar sync: Using Expo's expo-calendar library, you can integrate with the device's native calendar for seamless two-way sync.


Wrapping Up

In this tutorial, we walked through building a calendar and schedule management app with Rork — from setting up the calendar UI and event forms, to persisting data with AsyncStorage, and adding reminder notifications.

Building a calendar app is an excellent way to practice core mobile development skills: component design, state management, and data persistence. Use what you've learned here as a foundation, and customize it into the perfect scheduling tool for your target audience.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

◈ Getting Started2026-09-14
Rork No Longer Creates New Expo Projects: Keep Yours, or Rebuild It in SwiftUI?
You can no longer start an Expo project in Rork, but existing ones keep building and shipping. Here is how to decide whether to stay on Expo or rebuild in SwiftUI, and what quietly goes missing when you rebuild.
◈ Getting Started2026-09-08
Whether You Need Rork Max Is Answered by Your Permissions, Not the Price Page
When I cannot decide between standard Rork and Rork Max, I stop reading the price page. Instead I pull every OS capability my app actually asks for out of app.json and Info.plist, then sort them into what Expo covers, what a config plugin covers, and what truly needs native Swift.
◈ Getting Started2026-10-15
Should You Ship a Web Version While the Store Reviews Your App? Choosing a Release Order for a Rork App With Three Questions
Web first or store first for an app built in Rork? I checked Rork's FAQ and Expo's docs: Rork Pro and Rork Max give you different options, and three questions help you decide the release order.