GrundbegriffeGilt fürSuperScheduler Pro
Kontrollierte Ereignisse und Callbacks
Übergeben Sie die Ereignisse aus dem React-State und schreiben Sie sie in onEventsChange zurück. Das Control ruft diesen Callback einmal pro Task nach einem Ablegen, einer Dauer-Änderung oder einem Aufruf von control.events auf, mit der neuen Liste, den geänderten und entfernten Objekten und einem Grund. Geben Sie dem Control eine Kopie Ihres Arrays, denn es übernimmt das Array und bearbeitet es direkt. Die Bibliothek spricht nie mit Ihrem Backend: Speichern Sie in onEventMove, um vor dem Übernehmen der Änderung zu bestätigen, oder in onEventsChange, um optimistisch zu speichern und bei einem Fehler zurückzusetzen.
SuperScheduler Pro kennt zwei Arten, Ereignisdaten zu besitzen. Kontrolliert: Der React-State ist die maßgebliche Quelle, Sie übergeben ihn als events, und onEventsChange teilt Ihnen mit, was der Nutzer oder die API geändert hat. Unkontrolliert: Sie übergeben Anfangsdaten mit defaultEvents, und das Control führt seine eigene Liste. Kontrolliert ist der richtige Standard für eine Anwendung, die Änderungen speichert, sie an anderer Stelle der Seite anzeigt oder Rückgängig machen unterstützt.
Diese Seite erklärt das kontrollierte Muster, was der Änderungs-Callback erhält, die Regeln zum Besitz des Arrays, auf denen alles beruht, die genaue Reihenfolge der Callbacks beim Ablegen und an welcher Stelle Ihr Backend ins Spiel kommt.
Das kontrollierte Muster
import { useCallback, useMemo, useState } from 'react'
import { SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerEventsChangeArgs, SuperScheduler } from 'super-scheduler'
const ROOMS: SuperScheduler.ResourceData[] = [
{ id: 'r101', name: 'Room 101' },
{ id: 'r102', name: 'Room 102' },
]
const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
{ groupBy: 'Month' },
{ groupBy: 'Day', format: 'd' },
]
const INITIAL: SuperScheduler.EventData[] = [
{
id: 'b-1042',
resource: 'r101',
start: '2026-10-02T14:00:00',
end: '2026-10-05T11:00:00',
text: 'Booking 1042',
},
{
id: 'b-1043',
resource: 'r102',
start: '2026-10-03T14:00:00',
end: '2026-10-08T11:00:00',
text: 'Booking 1043',
},
]
export function ControlledPlanning() {
// React state is the single source of truth for the events.
const [events, setEvents] = useState<SuperScheduler.EventData[]>(INITIAL)
// The control adopts the array it receives and splices it in place: give it its own copy.
const owned = useMemo(() => events.slice(), [events])
// Once per task, after a drop, a resize or a control.events call. Handing the same objects back
// is recognised as an echo: the control does not reload or repaint.
const onEventsChange = useCallback((args: SchedulerEventsChangeArgs) => {
setEvents([...args.events])
}, [])
// Changes made outside the scheduler go to state; the control picks up the new array.
const addBlock = () =>
setEvents((current) => [
...current,
{
id: `block-${crypto.randomUUID()}`,
resource: 'r102',
start: '2026-10-12T00:00:00',
end: '2026-10-14T00:00:00',
text: 'Maintenance',
moveDisabled: true,
resizeDisabled: true,
},
])
return (
<>
<p>
{events.length} events{' '}
<button type="button" onClick={addBlock}>
Block Room 102
</button>
</p>
<SuperSchedulerComponent
startDate="2026-10-01"
days={31}
scale="Day"
timeHeaders={TIME_HEADERS}
resources={ROOMS}
events={owned}
onEventsChange={onEventsChange}
/>
</>
)
}Sie sollten zwei Buchungen und einen Ereigniszähler sehen. Ziehen Sie eine Buchung in das andere Zimmer: Sie bleibt dort, wo Sie sie abgelegt haben, weil ihre neue Position jetzt im React-State steht. Drücken Sie den Button: Der Zähler steigt, und auf Room 102 erscheint ein Wartungsblock, gesperrt gegen Verschieben und Dauer ändern.
Drei Zeilen tragen das Muster:
useStatehält die Ereignisse. Alles, was sie anzeigt oder bearbeitet, liest diesen State.useMemo(() => events.slice(), [events])gibt dem Control seine eigene Kopie des Arrays (siehe Besitz des Arrays).onEventsChangeschreibt die neue Liste des Controls mitsetEvents([...args.events])in den State zurück.
Ändert sich der State aus einem anderen Grund (ein Formular, ein Server-Push, der Button oben), erreicht das neue Array das Control als geänderte Prop, und das Control lädt es neu.
Was onEventsChange erhält
onEventsChange wird aufgerufen, nachdem sich der Ereignisspeicher des Controls geändert hat, und zwar höchstens einmal pro Task: Mehrere Änderungen im selben synchronen Block kommen gemeinsam in einem Aufruf an, im nächsten Microtask.
| Argument | Inhalt |
|---|---|
events | Die vollständige Liste des Controls nach der Änderung, als Datenobjekte |
changed | Durch diese Änderung hinzugefügte oder ersetzte Objekte, in ihrem neuen Zustand |
removed | Durch diese Änderung entfernte oder ersetzte Objekte, in ihrem vorherigen Zustand |
reason | Warum sich der Speicher geändert hat (siehe unten) |
reason | Ausgelöst durch |
|---|---|
'move' | Ein abgeschlossenes Drag-and-drop, auch per Tastatur, sowie Ablegen von außerhalb des Planers |
'resize' | Eine abgeschlossene Dauer-Änderung |
'create' | control.events.add() |
'update' | control.events.update() mit einem neuen Objekt oder ein Hinzufügen und ein Entfernen im selben Task |
'remove' | control.events.remove(), einschließlich des eingebauten Lösch-Buttons (eventDeleteHandling: 'Update') |
'history' | Rückgängig machen oder Wiederholen, vom Control über super-scheduler/history angewendet |
'load' | Sie haben andere Ereignisobjekte als events übergeben, oder ein Bereichslader hat neu geladene Ereignisse eingefügt |
'api' | Andere Änderungen, die die Bibliothek von sich aus am Speicher vornimmt; behandeln Sie sie wie 'update' |
Bei einer Verschiebung enthält changed das neue Objekt und removed das Objekt, das es ersetzt hat: Sie haben den Zustand vorher und nachher, ohne eine eigene Kopie zu führen. Die Objekte in events behalten ihre Identität zwischen den Aufrufen, sofern sie sich nicht geändert haben; React.memo und Selektoren, die per Referenz vergleichen, funktionieren also weiter.
Unkontrolliert: defaultEvents
Übergeben Sie defaultEvents statt events, wenn der Planer die Daten besitzen darf, etwa in einer überwiegend lesenden Ansicht oder einem Prototyp. Das Array wird einmal gelesen, bei der Initialisierung; spätere Änderungen der Prop werden mit einer Warnung in der Entwicklung ignoriert. Übergeben Sie beide, gewinnt events, ebenfalls mit einer Warnung.
In diesem Modus lesen Sie die aktuellen Daten aus control.events.list, abonnieren sie mit useScheduler({ track: ['events'] }) aus super-scheduler/hooks oder hören weiterhin auf onEventsChange, das in beiden Modi funktioniert.
Das Control übernimmt Ihr Array
Aus Geschwindigkeitsgründen kopiert das Control das Array, das Sie als events übergeben, nicht: control.events.list ist dieses Array, und Hinzufügen, Entfernen und Ablegen bearbeiten es direkt mit splice. Deshalb übergibt das Muster oben eine Kopie. Ohne die Kopie würde das Control das Array in Ihrem React-State verändern, an React vorbei.
Dieselbe Regel erklärt das übrige Verhalten der kontrollierten Schleife:
- Echos kosten nichts. Speichert
onEventsChange[...args.events], rendert React, und das Control erhält ein Array mit genau den Objekten, die es bereits hält. Es erkennt das Echo und tut nichts: kein Neuladen, kein Neuzeichnen. - Neue Objekte lösen ein Neuladen aus. Enthält Ihr State Objekte, die das Control noch nicht gesehen hat (eine Bearbeitung im Formular, eine Serverantwort), lädt es seine Liste aus dem neuen Array neu und meldet dann
reason: 'load'. Diese Liste erneut zu speichern ist ein Echo, und die Schleife endet dort. - Frieren Sie das Array nicht ein, wenn Sie
control.events.add,updateoderremoveaufrufen: Sie bearbeiten es direkt und werfen bei einem eingefrorenen Array einenTypeError. Ablegen und Dauer ändern kopieren ein eingefrorenes Array vorher.
Reihenfolge der Callbacks beim Ablegen
Jedes Drag-and-drop durchläuft eine feste Abfolge. Dieser Logger macht sie sichtbar:
import type { SchedulerProps } from 'super-scheduler'
// Logs every callback of one drag-and-drop, in the order the library calls them.
export const tracing: SchedulerProps = {
onEventMoving: (args) =>
console.debug('1. moving (every shadow change)', args.start.value, args.allowed),
onEventMove: (args) =>
console.debug('2. move (before the commit, cancelable)', args.newStart.value),
onEventMoved: (args) =>
// The store already holds the new times here.
console.debug(
'3. moved (after the commit)',
args.control.events.find(args.e.id())?.start().value,
),
onEventsChange: (args) =>
console.debug('4. eventsChange (next microtask)', args.reason, args.changed.length),
}onEventMovingläuft bei jeder Änderung des Schattens, während der Nutzer zieht. Es kann die Position ablehnen oder anpassen (siehe Ziehen, Dauer ändern und Geschäftsregeln).- Wurde beim Loslassen die letzte Position abgelehnt (durch Ihre Regel, eine Überlappung, eine gesperrte Zelle), läuft nichts weiter: kein
onEventMove, keine Änderung. onEventMoveläuft einmal, bevor sich der Speicher ändert. Es kann mitargs.preventDefault()abbrechen,args.newStart,args.newEndoderargs.newResourceändern oder die Entscheidung mitargs.async = trueundargs.loaded()aufschieben.- Der Speicher wird aktualisiert (mit
eventMoveHandling: 'Update', dem Standard). onEventMovedläuft nach dem Übernehmen:args.control.events.find(id)liefert bereits die neuen Zeiten.onEventsChangeläuft im nächsten Microtask mitreason: 'move'.
Das Ändern der Dauer folgt derselben Abfolge mit onEventResizing, onEventResize, onEventResized und reason: 'resize'. Da onEventMoved läuft, bevor React etwas gespeichert hat, lesen Sie die neuen Werte aus seinen Argumenten, nicht aus Ihrem State.
Wo Ihr Backend ins Spiel kommt
Der Planer ruft nie einen Server auf. Sie entscheiden, wann gespeichert wird, und es gibt zwei solide Ansätze.
Bestätigen, bevor die Änderung übernommen wird
Speichern Sie in onEventMove oder onEventResize mit args.async = true und rufen Sie args.loaded() auf, wenn der Server antwortet; hat er abgelehnt, rufen Sie vorher args.preventDefault() auf. Bis dahin bleibt das Ereignis, wo es war, der Bildschirm zeigt also nie eine Änderung, die der Server abgelehnt hat. Der Preis ist eine sichtbare Verzögerung bei jedem Ablegen. Das vollständige Muster steht unter Beim Ablegen bestätigen.
Optimistisch speichern und bei einem Fehler zurücksetzen
Übernehmen Sie die Änderung sofort, speichern Sie im Hintergrund und setzen Sie das vorherige Objekt wieder ein, wenn das Speichern fehlschlägt. onEventsChange liefert alles, was Sie brauchen: changed ist das, was gespeichert werden muss, removed das, was wiederherzustellen ist.
import { useCallback, useMemo, useState } from 'react'
import { SuperSchedulerComponent, useSchedulerControl } from 'super-scheduler'
import type { SchedulerEventsChangeArgs, SuperScheduler } from 'super-scheduler'
const iso = (value: SuperScheduler.DateInput) => (typeof value === 'string' ? value : value.value)
interface Props {
readonly rooms: SuperScheduler.ResourceData[]
readonly initial: SuperScheduler.EventData[]
}
export function OptimisticPlanning({ rooms, initial }: Props) {
const [events, setEvents] = useState(initial)
const owned = useMemo(() => events.slice(), [events])
const { controlRef } = useSchedulerControl()
const onEventsChange = useCallback(
(args: SchedulerEventsChangeArgs) => {
// 1. Show the change immediately.
setEvents([...args.events])
if (args.reason !== 'move' && args.reason !== 'resize') return
for (const after of args.changed) {
// The object this drop replaced: the state to restore if the server says no.
const before = args.removed.find((item) => item.id === after.id)
if (before === undefined || after.resource === undefined) continue
// 2. Persist it.
saveBooking({
id: String(after.id),
resource: String(after.resource),
start: iso(after.start),
end: iso(after.end),
})
// 3. Revert on failure. Matching by identity leaves a newer change of the same event alone.
.catch(() => {
setEvents((current) => current.map((item) => (item === after ? before : item)))
controlRef.current?.message('The change could not be saved and was undone.')
})
}
},
[controlRef],
)
return (
<SuperSchedulerComponent
controlRef={controlRef}
startDate="2026-10-01"
days={31}
scale="Day"
resources={rooms}
events={owned}
onEventsChange={onEventsChange}
/>
)
}Ein Ablegen sollte sofort wirken. Lehnt saveBooking ab, springt die Buchung an ihren vorherigen Platz zurück, und eine Meldung erklärt den Grund. Das Zurücksetzen gleicht per Objektidentität ab; hat der Nutzer dieselbe Buchung inzwischen erneut verschoben, bleibt die neuere Änderung unangetastet.
Für welchen Ansatz Sie sich auch entscheiden, diese Aufgaben bleiben in Ihrer Anwendung:
- Auf dem Server validieren. Regeln in
onEventMovingdienen der Benutzerführung; der Server muss Überlappungen, Berechtigungen und Geschäftsregeln erneut prüfen, weil andere Nutzer und andere Clients dieselben Daten ändern. - Normalisieren, was Sie senden. Verschobene Ereignisse tragen
SuperScheduler.Date-Werte, unberührte Ihre Strings. Siehe Werte nach dem Ziehen. - Die Version des Servers übernehmen. Gibt der Server ein kanonisches Objekt zurück (eine neue ID für ein angelegtes Ereignis, einen neu berechneten Preis), ersetzen Sie das Objekt im State. Das Control lädt neu und meldet
reason: 'load'.
Rückgängig machen, Bereiche und bereichsweises Laden
- Rückgängig machen und Wiederholen.
createHistory({ apply })aussuper-scheduler/historykann Rückgängig machen und Wiederholen auf Ihren State statt auf das Control anwenden. Siehe Rückgängig machen und Wiederholen. - Mehrere Bereiche.
SchedulerPanesteilt eine Ereignisliste zwischen Bereichen über kontrollierteeventsundonEventsChange(oderdefaultEvents). Siehe Bereiche und gespeicherte Ansichten. - Laden nach Datumsbereich. Ein Bereichslader aus
super-scheduler/rangesfügt ein, was er lädt, und meldet es überonEventsChangemitreason: 'load'; übernehmen Sie diese Liste. Siehe Bereichsweises Laden.
Disposition im technischen AußendienstEin dringender Auftrag kommt herein. Finden Sie das Team, das ihn rechtzeitig übernehmen kann. Planung von SchulungsräumenDie Anmeldungen sprengen den Raum. Beide Termine wählen, sehen, was für beide frei ist, zusammen verschieben und die Ansicht behalten.
Nächste Schritte
- Ungültige Verschiebungen schon während des Ziehens ablehnen: Ziehen, Dauer ändern und Geschäftsregeln.
- Eigene Felder durchgängig typisieren: Eigene Felder mit EventData<T>.
- Das Control aus React-Code erreichen: React-Integration.
Verwandte Beispiele
- FieldworkDisposition im technischen AußendienstEin dringender Auftrag kommt herein. Finden Sie das Team, das ihn rechtzeitig übernehmen kann.
- MorrowPlanung von SchulungsräumenDie Anmeldungen sprengen den Raum. Beide Termine wählen, sehen, was für beide frei ist, zusammen verschieben und die Ansicht behalten.