Skip to content

React Hooks

useCalendar, useSchedule, and useTimeline are the hook-shaped alternative to managing your own useRef. Each one returns:

  • ref — a callback ref to spread on the component (<Schedule ref={ref} />).
  • instance — a reactive state value. null before mount, the lemonadejs instance after. React re-renders when it transitions, so you can drive UI off of "is the component ready?".

The plain useRef pattern is still supported — pick whichever fits your component shape better.

A manual useRef works, but its .current is not reactive — React doesn't re-render when it's assigned. The hook wraps that handshake in useState so you can do things like:

  • disable buttons until the component has mounted
  • conditionally render toolbars based on instance state
  • capture the instance for sibling components without forwarding refs
import { Schedule, useSchedule } from '@calendarjs/react';

export default function App() {
    const { ref, instance } = useSchedule();

    return (
        <>
            <Toolbar disabled={!instance} onAdd={() => instance.addEvents([{ start: '09:00' }])} />
            <Schedule ref={ref} />
        </>
    );
}
import { useState } from 'react';
import { Schedule, useSchedule } from '@calendarjs/react';

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

    const addQuickEvent = () => {
        instance.addEvents([
            { start: '09:00', end: '10:00', date: '2025-01-15', title: 'Quick' },
        ]);
    };

    return (
        <>
            <button disabled={!instance} onClick={addQuickEvent}>
                Add quick event
            </button>
            <button disabled={!instance} onClick={() => instance.undo()}>
                Undo
            </button>
            <button disabled={!instance} onClick={() => instance.redo()}>
                Redo
            </button>

            <Schedule
                ref={ref}
                type="week"
                value="2025-01-15"
                data={events}
                onCreate={(created) => setEvents((prev) => [...prev, ...created])}
                style={{ height: 500 }}
            />
        </>
    );
}
import { useState } from 'react';
import { Calendar, useCalendar } from '@calendarjs/react';

export default function CalendarApp() {
    const { ref, instance } = useCalendar();
    const [date, setDate] = useState('2025-01-15');

    return (
        <>
            <div style={{ display: 'flex', gap: 8 }}>
                <button disabled={!instance} onClick={() => instance.prev()}>Prev</button>
                <button disabled={!instance} onClick={() => instance.setValue(new Date())}>Today</button>
                <button disabled={!instance} onClick={() => instance.next()}>Next</button>
            </div>

            <Calendar ref={ref} type="inline" value={date} onChange={setDate} />
        </>
    );
}
import { useState } from 'react';
import { Timeline, useTimeline } from '@calendarjs/react';

export default function TimelineApp() {
    const { ref, instance } = useTimeline();
    const [data, setData] = useState([
        { date: '2025-01-01', title: 'Project Started' },
        { date: '2025-02-01', title: 'Milestone 1' },
    ]);

    return (
        <>
            <div>
                <button disabled={!instance} onClick={() => instance.prev()}>Previous month</button>
                <button disabled={!instance} onClick={() => instance.next()}>Next month</button>
            </div>

            <Timeline ref={ref} type="monthly" value="2025-01-01" data={data} />
        </>
    );
}

The hooks are typed against the corresponding Instance type from each upstream namespace:

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

export default function App() {
    // instance is `ScheduleNS.Instance | null`
    const { ref, instance } = useSchedule();

    if (instance) {
        // Fully typed — autocompletes addEvents, getData, undo, etc.
        instance.addEvents([{ start: '09:00' }]);
    }

    return <ScheduleNS /* ... */>{null}</ScheduleNS>;
}

Wrap the built-in hook for app-specific behaviour — autosave, default options, instrumented actions:

import { useCallback, useEffect } from 'react';
import { useSchedule } from '@calendarjs/react';

export function useAutosaveSchedule(persist) {
    const { ref, instance } = useSchedule();

    useEffect(() => {
        if (!instance) return;
        const handler = () => persist(instance.getData());
        const id = setInterval(handler, 30_000);
        return () => clearInterval(id);
    }, [instance, persist]);

    const addAndSave = useCallback(
        (events) => {
            const created = instance?.addEvents(events);
            if (created) persist(instance.getData());
            return created;
        },
        [instance, persist],
    );

    return { ref, instance, addAndSave };
}

The hooks return a callback ref — perfect when you want a reactive instance. If you don't need re-renders driven by mount state, plain useRef is one less re-render and one less import:

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

export default function App() {
    const ref = useRef();
    return <Schedule ref={ref} data={[]} />;
}

Both patterns are fully supported and can be mixed in the same app.