Skip to main content

Import and Export

This guide explains how to move events between the Calendar widget and external sources - iCalendar (.ics) files and plain JSON arrays. You'll see how to seed the calendar from existing data, save user changes back out, and round-trip events through a backend.

How import and export work

Calendar events live in a single array passed to the events prop. Every entry is a CalendarEvent with id, start, end, an optional allDay flag, and any custom fields you want to keep alongside it. The widget doesn't care where that array came from - JSON fetched from an API, a static file, or a parsed .ics payload all work the same.

For iCalendar interchange, the package re-exports two helpers from @wx/calendar-ical:

Both helpers map a small, common subset of the iCal spec:

iCal fieldCalendarEvent field
UIDid (kept as a number when numeric, string otherwise)
DTSTART / DTENDstart, end (real Date instances)
DTSTART;VALUE=DATEstart plus allDay: true
SUMMARYtext
DESCRIPTIONdescription

Recurrence rules, attendees, alarms, and timezone overrides aren't translated - only the basics above. If you need richer iCal, parse the file yourself or extend the helpers.

For JSON, there's no helper at all. You're working directly with CalendarEvent[], so any source that returns objects with id, start, and end works. Just remember to revive date strings into Date instances before handing the array to the widget.

To read the current state of the calendar (after the user has added, edited, or deleted events), grab the instance API and call api.getEvents(). That's the array you serialize.

Seeding events from JSON

The simplest case - fetch a JSON list and pass it to events:

import { useState, useEffect } from "react";
import { Calendar } from "@svar-ui/react-calendar";

function App() {
const [events, setEvents] = useState([]);

useEffect(() => {
fetch("/api/events")
.then(r => r.json())
.then(raw => {
setEvents(raw.map(e => ({
...e,
start: new Date(e.start),
end: new Date(e.end),
})));
});
}, []);

return <Calendar events={events} date={new Date()} />;
}

The widget needs start and end as real Date objects. JSON parses them as strings, so wrap them with new Date(...) before passing the array in.

Importing an .ics file

Let users upload a calendar file and replace the current events with its contents:

import { useState } from "react";
import { Calendar, parseICal } from "@svar-ui/react-calendar";

function App() {
const [events, setEvents] = useState([]);

const importIcal = e => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = ev => {
setEvents(parseICal(ev.target.result));
};
reader.readAsText(file);
};

return (
<>
<input type="file" accept=".ics" onChange={importIcal} />
<Calendar events={events} date={new Date()} />
</>
);
}

parseICal returns a fresh array, so calling setEvents is enough to refresh the view. Each event already has parsed Date instances - no extra conversion needed.

Exporting to .ics

To save the current calendar state as a downloadable file, read events from the API and pass them to serializeICal:

import { useRef } from "react";
import { Calendar, Editor, serializeICal } from "@svar-ui/react-calendar";

function App() {
const api = useRef(null);

const exportIcal = () => {
const ics = serializeICal(api.current.getEvents());
const blob = new Blob([ics], { type: "text/calendar" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "calendar.ics";
a.click();
URL.revokeObjectURL(url);
};

return (
<>
<button onClick={exportIcal}>Export .ics</button>
<Calendar ref={api} events={[]} date={new Date()} />
{api.current && <Editor api={api.current} />}
</>
);
}

api.current.getEvents() returns the live event list, including anything the user just added or edited. Wrap the serialized string in a Blob and trigger a download via a temporary anchor.

Round-tripping through a backend

The same helpers work on either side of an HTTP call. To upload an .ics file:

const ics = serializeICal(api.current.getEvents());
await fetch("/api/calendar.ics", {
method: "POST",
headers: { "Content-Type": "text/calendar" },
body: ics,
});

To pull one back:

const text = await fetch("/api/calendar.ics").then(r => r.text());
setEvents(parseICal(text));

For ongoing CRUD against a JSON backend, skip serialization entirely and use RestDataProvider instead - it forwards add-event, update-event, and delete-event over REST automatically. See the data provider guide for the full setup.

Clearing the calendar

Replace the array with an empty one:

setEvents([]);

The widget reacts to the change and clears the grid. If you have an Editor mounted, its bound event clears too.

What's not covered by the iCal helpers

  • Recurrence (RRULE) - the parser strips line parameters and doesn't emit rrule on the event. If you need recurring events from .ics, post-process the parsed events or extend parseICal.
  • Timezones (TZID) - DTSTART;TZID=... keeps the wall-clock time in the user's local zone. UTC values (with a trailing Z) and floating values are supported; named zones are not.
  • Attendees, alarms, attachments - not parsed, not emitted.

For any of these, treat the .ics round-trip as lossy and keep the canonical event data on your backend in JSON.