Skip to main content

Data Binding & State Management in React Scheduler

React Scheduler supports two data binding patterns:

  1. React state as the source of truth (recommended for most React apps)
  2. Scheduler as the source of truth (useful for specialized, high-throughput cases)

Both models are valid. Pick one model per screen and keep it consistent.

If you have not rendered a basic chart yet, start with Quick Start.

Data models​

In this model:

  • you keep events (and often view / date) in React state or a state manager
  • you pass that state into <ReactScheduler /> props
  • Scheduler calls data.save / data.batchSave when users edit data
  • you update state, and React re-renders Scheduler with the new props

Use this when other React components must stay synchronized with Scheduler data.

Scheduler as the source of truth​

In this model:

  • Scheduler loads and mutates data internally
  • you optionally forward edits to backend endpoints
  • React does not mirror every event update in state

Use this model when React does not need to immediately reflect every individual Scheduler change.

React state as source of truth​

Minimal example​

import { useMemo, useState } from "react";
import ReactScheduler, { type Event } from "@dhtmlx/trial-react-scheduler";
import "@dhtmlx/trial-react-scheduler/dist/react-scheduler.css";

import { seedEvents } from "./seed/data";

export default function ReactStateScheduler() {
const [events, setEvents] = useState<Event[]>(seedEvents);

const data = useMemo(
() => ({
save: (entity: string, action: string, item: Event, id: string | number) => {
if (entity !== "event") return;

if (action === "create") {
setEvents((prev) => [...prev, item]);
return;
}

if (action === "update") {
setEvents((prev) => prev.map((e) => (e.id === id ? item : e)));
return;
}

if (action === "delete") {
setEvents((prev) => prev.filter((e) => e.id !== id));
}
},
}),
[]
);

return (
<div style={{ height: "100vh" }}>
<ReactScheduler events={events} data={data} />
</div>
);
}

This pattern makes React the canonical owner of event data.

Handling updates with data.save​

data.save is called for each user change:

(entity: string, action: string, item: any, id: string | number) => void | Promise<any>

For Scheduler event CRUD:

  • entity is "event"
  • action is "create" | "update" | "delete"
  • item is the created/updated/deleted event
  • id is the affected event id

Backend-oriented example​

const data = {
save: async (entity: string, action: string, item: any, id: string | number) => {
if (entity !== "event") return;

if (action === "create") {
const response = await fetch("/api/events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(item),
});

const created = await response.json();
return { id: created.id };
}

if (action === "update") {
await fetch(`/api/events/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(item),
});
return;
}

if (action === "delete") {
await fetch(`/api/events/${id}`, { method: "DELETE" });
}
},
};

If your backend replaces temporary IDs on create, return { id: realId } so Scheduler can reconcile client and server IDs.

Bulk updates with data.batchSave​

data.batchSave is useful when many changes are emitted in a short time (for example, dense editing sessions).

Use it when you want to:

  • reduce request count by sending grouped changes
  • process updates in one reducer/store transaction
<ReactScheduler
events={events}
data={{
batchSave: async (changes) => {
await fetch("/api/events/batch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(changes),
});
},
}}
/>

Use save for simple per-change logic, and batchSave for grouped synchronization.

Loading data into React state​

In the React-driven model, Scheduler gets data from React state. Common sources:

  • local component state
  • global state manager (Redux Toolkit, Zustand, MobX, XState, Jotai, Valtio)
  • API calls

Local state source​

import { useState } from "react";
import ReactScheduler, { type Event } from "@dhtmlx/trial-react-scheduler";
import { seedEvents, seedView, seedDate } from "./seed/data";

export default function LocalStateExample() {
const [events] = useState<Event[]>(seedEvents);

return (
<ReactScheduler
events={events}
view={seedView}
date={seedDate}
/>
);
}

State manager source​

Every library follows the same flow:

  • selector/hook reads state
  • props feed Scheduler
  • data.save dispatches actions/store mutations
const events = useSchedulerStore((s) => s.events);
const onSave = useSchedulerStore((s) => s.handleSave);

<ReactScheduler events={events} data={{ save: onSave }} />;

State-manager tutorials:

API loading source​

import { useEffect, useState } from "react";
import ReactScheduler, { type Event } from "@dhtmlx/trial-react-scheduler";

export default function SchedulerWithApi() {
const [events, setEvents] = useState<Event[] | null>(null);

useEffect(() => {
let disposed = false;

(async () => {
const response = await fetch("/api/events");
const payload = await response.json();
if (!disposed) setEvents(payload.events || []);
})();

return () => {
disposed = true;
};
}, []);

if (!events) return <div>Loading Scheduler...</div>;

return <ReactScheduler events={events} />;
}

Scheduler as source of truth​

In this mode, React renders the component but does not hold canonical event state.

URL transport example​

<ReactScheduler
data={{
load: "/api/scheduler/load",
save: "/api/scheduler/save",
}}
/>

Callback transport example​

<ReactScheduler
data={{
load: async () => {
const response = await fetch("/api/scheduler/load");
return response.json();
},
save: async (entity, action, item, id) => {
await fetch("/api/scheduler/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ entity, action, item, id }),
});
},
}}
/>

Use this approach when Scheduler can remain the primary runtime store and React does not need to render every single update.

Choosing the right model​

Use React-driven model when:

  • multiple React components depend on Scheduler data
  • you need predictable global state integration
  • you want straightforward undo/redo in app state

Use Scheduler-driven model when:

  • React is mostly shell/layout
  • you prefer Scheduler-managed runtime mutations
  • server transport is the primary synchronization mechanism

What's next​

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.