Skip to content

React Schedule

4.2KB gzipped

The Schedule component is an event scheduling interface for React applications. It supports day, week, and weekdays views with drag-and-drop event management. Built for appointment systems, team calendars, and resource scheduling.

  • Day, week, and weekdays views
  • Drag and drop events
  • Create, edit, delete events
  • Resource/calendar scheduling
  • Keyboard navigation

CalendarJS is a complete calendar and scheduling solution that includes:

  • Calendar - Date picker with range selection (3.18KB)
  • Schedule - Event scheduler with drag & drop (this page)
  • Timeline - Chronological event display (2.1KB)

Before scheduling events, users often need to select dates. The Calendar component provides:

  • Single and range date selection
  • Date validation and restrictions
  • International date formats
  • Keyboard navigation

Perfect for: Date pickers in booking forms, availability selectors, report date ranges

Explore Calendar Component →

For displaying events in a chronological list format instead of a calendar grid, use the Timeline component:

  • Event logs and activity feeds
  • Project roadmaps and milestones
  • Monthly grouped views
  • Ascending/descending sort

Perfect for: Activity logs, project timelines, event history, roadmaps

Explore Timeline Component →

For reactive applications, LemonadeJS Timeline provides:

  • Reactive data binding
  • Vue, React, Angular integration
  • Built on CalendarJS Timeline

Explore LemonadeJS Timeline →


npm install @calendarjs/react react
PropertyDescription
type?: 'week' | 'day' | 'weekdays'Schedule view type. Default: 'week'.
weekly?: booleanWeekly schedule mode. No date associated to columns, just day of week. Default: false.
value?: stringInitial date in ISO format (YYYY-MM-DD).
data?: EventInput[]Array of event objects to display. Loose input shape — only start is required (see below).
grid?: numberGrid time interval in minutes. Default: 15.
snap?: numberSnap step (in minutes) for create/resize gestures. Independent of grid — e.g. grid: 15, snap: 60 keeps 15-minute rows visible but locks event duration to 60-minute increments. Defaults to grid.
overlap?: booleanAccept time overlap between events. Default: false.
validRange?: string[]Valid time range [startTime, endTime] in HH:MM format.
readOnlyRange?: string[] | string[][]Read-only time range. Single: ['08:00', '18:00'] or multiple: [['00:00', '08:00'], ['18:00', '23:59']].

The package distinguishes the input shape (what you pass into data, addEvents, updateEvent) from the resolved shape (what callbacks and getData() hand back, with defaults filled in). See the TypeScript guide for full details.

Only start is required; the component fills in defaults (guid, title, color, type) on render. An index signature lets you attach app-specific metadata (class_id, teacher_id, ...) without casts.

PropertyDescription
start: stringRequired. Start time in HH:MM format.
end?: stringEnd time in HH:MM format.
title?: stringEvent title displayed on the schedule.
color?: stringEvent background color (hex).
guid?: stringUnique identifier (auto-generated if omitted).
type?: string | nullEvent type/category.
date?: stringEvent date in YYYY-MM-DD format. Used when weekly is false.
weekday?: numberDay of the week (0-6, Sunday is 0). Used when weekly is true.
guests?: stringGuests list.
location?: stringEvent location.
description?: stringEvent description.
readonly?: booleanEvent is read-only (cannot be edited).
visible?: booleanEvent visibility.
warning?: booleanShow warning border on event.
[key: string]: anyApp-specific metadata (no cast required).

Returned by getData() and passed to callbacks. Extends EventInput with guid, title, color, and type always present.

interface Event extends EventInput {
    guid: string;
    title: string;
    color: string;
    type: string | null;
}

Use the camelCase names in React (e.g. onCreate instead of oncreate). Handlers receive just the data — the Schedule instance is available via the forwarded ref.

EventDescription
onBeforeCreateBefore events are created. Return false to cancel. Always called with an array (length ≥ 1).
onBeforeCreate?: (events: Event[], e?: MouseEvent) => boolean | void
onCreateCalled after events are created. Always called with an array (length ≥ 1). The MouseEvent is forwarded for interactive creates.
onCreate?: (events: Event[], e?: MouseEvent) => void
onBeforeInsertPer-event hook during the drag-to-create gesture. Return a modified event, or false to drop it.
onBeforeInsert?: (event: Event) => Event | boolean | void
onDblClickCalled on double-click of an event.
onDblClick?: (event: Event) => void
onEditionTriggered when an event is being edited (double click or after creation).
onEdition?: (event: Event) => void
onBeforeChangeEventBefore a drag/resize change is applied to an event. Return false to cancel.
onBeforeChangeEvent?: (state: object) => boolean | void
onChangeEventCalled after updateEvent applies changes.
onChangeEvent?: (event: Event, oldValue: Partial<Event>, newValue: Partial<Event>) => void
onDeleteCalled for each event deleted via deleteEvents or the Delete key.
onDelete?: (event: Event) => void
onBeforeChangeFires before updateEvent / deleteEvents / setData. Return false to cancel. state.action identifies the operation.
onBeforeChange?: (state: { action: 'updateEvent' | 'deleteEvents' | 'setData', ...rest }) => boolean | void
onChangeFires after updateEvent / deleteEvents / setData and after an interactive create.
onChange?: () => void
onErrorFires when an action is rejected (invalid range, conflict, disabled slot, ...).
onError?: (message: string) => void

Methods are called via the forwarded ref: ref.current.addEvents(...).

MethodDescription
addEvents(events: EventInput | EventInput[], e?: MouseEvent): Event[] | falseAdd one or more events. Accepts the loose EventInput shape — defaults are filled in.
updateEvent(guidOrEvent: string | Event, newValue: Partial<EventInput>): void | falseUpdate an event by guid or reference. Returns false if the change is rejected (onBeforeChange, conflict, disabled range).
deleteEvents(events: Event | Event[] | string | string[]): void | falseDelete one or more events. Returns false if onBeforeChange cancels.
getData(): Event[]Get all events data (resolved shape).
setData(data: EventInput[]): void | falseReplace all events. Accepts the loose EventInput shape. Returns false if onBeforeChange cancels.
getEvent(guid: string): HTMLElement | nullGet event DOM element by guid. Returns null if not found.
setRange(range: string[]): voidSet valid time range.
setReadOnly(range: string[] | string[][]): voidSet read-only time range.
resetSelection(e?: MouseEvent): voidReset current selection.
render(): voidRender the schedule.
next(): voidNavigate to the next period (day / week depending on type).
prev(): voidNavigate to the previous period.
today(): voidReset value to today's date.
undo(): voidUndo last change.
redo(): voidRedo last undone change.

Read on the underlying instance via ref.current.

PropertyDescription
type: 'week' | 'day' | 'weekdays'Current schedule view type.
weekly: booleanWeekly schedule mode.
value: stringCurrent date value in ISO format.
data: Event[]Array of events.
grid: numberGrid time interval in minutes.
snap: numberSnap step (in minutes) for create/resize gestures.
overlap: booleanAccept time overlap between events.
validRange?: string[]Valid time range.
readOnlyRange?: string[] | string[][]Read-only time range.
el: HTMLElementThe root element.
tbody: HTMLTableSectionElementThe <tbody> element holding all time-row cells.
headers: Array<{ day: string, weekday: number, value: string, selected: boolean, el: HTMLElement }>Header row entries (one per visible column).
pointer: HTMLElementElement used to render the current-time pointer.
import { useState } from 'react';
import { Schedule } from '@calendarjs/react';

export default function App() {
    const [events] = useState([
        { guid: 'evt-1', title: 'Team Meeting', date: '2025-12-03', start: '10:00', end: '11:00', color: '#3498db' },
        { guid: 'evt-2', title: 'Project Review', date: '2025-12-04', start: '14:00', end: '15:30', color: '#e74c3c' }
    ]);

    return <Schedule type="week" value="2025-12-01" data={events} style={{ height: '500px' }} />;
}

onCreate is always called with an array (length ≥ 1) — no Array.isArray unwrap needed.

import { useState } from 'react';
import { Schedule } from '@calendarjs/react';

export default function App() {
    const [events] = useState([]);

    return (
        <Schedule
            type="week"
            value="2025-12-01"
            data={events}
            style={{ height: '500px' }}
            onCreate={(events) => events.forEach((ev) => console.log('Created:', ev.title))}
            onChangeEvent={(event) => console.log('Updated:', event.title)}
            onDelete={(event) => console.log('Deleted:', event.title)}
        />
    );
}

Replace the array reference (don't mutate in place) and the schedule re-renders:

import { useCallback, useState } from 'react';
import { Schedule } from '@calendarjs/react';

export default function App() {
    const [events, setEvents] = useState([
        { start: '09:00', end: '10:00', date: '2025-01-15', title: 'Standup' },
    ]);

    // Memoize so the wrapper's prop diff sees a stable reference.
    const onCreate = useCallback(
        (created) => setEvents((prev) => [...prev, ...created]),
        [],
    );
    const onDelete = useCallback(
        (event) => setEvents((prev) => prev.filter((e) => e.guid !== event.guid)),
        [],
    );

    return (
        <Schedule
            type="week"
            value="2025-01-15"
            data={events}
            onCreate={onCreate}
            onDelete={onDelete}
            style={{ height: 500 }}
        />
    );
}

The hook returns a callback ref plus a reactive instance — null before mount, the live instance after. Useful when you want to drive UI off of "is the schedule ready?".

import { useState } from 'react';
import { Schedule, useSchedule } from '@calendarjs/react';

export default function App() {
    const { ref, instance } = useSchedule();
    const [events, setEvents] = useState([]);

    return (
        <>
            <button
                disabled={!instance}
                onClick={() => instance.addEvents([{ start: '09:00', end: '10:00' }])}
            >
                Add event
            </button>
            <button disabled={!instance} onClick={() => instance.undo()}>Undo</button>

            <Schedule
                ref={ref}
                type="week"
                value="2025-01-15"
                data={events}
                onCreate={(created) => setEvents((prev) => [...prev, ...created])}
                style={{ height: 500 }}
            />
        </>
    );
}

The EventInput index signature accepts arbitrary keys — no as any:

import { Schedule } from '@calendarjs/react';
import type { Schedule as ScheduleNS } from '@calendarjs/react';

const events: ScheduleNS.EventInput[] = [
    { start: '09:00', end: '10:00', class_id: 42, teacher_id: 7 },
    { start: '10:00', end: '11:00', class_id: 42, teacher_id: 7 },
];

const onCreate = (created: ScheduleNS.Event[]) => {
    created.forEach((ev) => console.log(ev.guid, ev.class_id));
};

<Schedule data={events} onCreate={onCreate} />

See TypeScript for the full input-vs-resolved type guide.