What You Will Learn in This Tutorial
How to add a React Scheduler component to your application..
How to define the visible date range (one year) and scroll to the current date.
How to load reservation data.
How to change event/reservation color.
How to enable drag and drop operations (moving, resizing).
How to show icons in the row headers.
How to add context menu to reservations.
License
Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript.
Generate a React Scheduler Project using an Online UI Builder App
To get started more easily, you can use the online UI Builder app to configure the React Scheduler component:
Configure the component using the available preset values.
Preview your changes immediately with a live Scheduler instance.
Download a complete React project that includes your selected configuration.
Create a React Scheduler Component using 6 Lines of Code
To integrate a visual scheduler into your React application, the following lines of code are all you need:
import React from 'react';
import { DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
return <DayPilotScheduler />;
}
export default Scheduler;
This will render an empty Scheduler component with the default configuration, without any resources or events/reservations.
This code requires the daypilot-pro-react
library which can be installed using npm:
npm install https://npm.daypilot.org/daypilot-pro-react/trial/2024.3.5973.tar.gz
You can get the latest version of daypilot-pro-react
at npm.daypilot.org.
How to Load Scheduler Rows
In order to display any useful data, we need to define rows using the resources attribute:
import React from 'react';
import { DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
return (
<DayPilotScheduler
treeEnabled={true}
resources={[
{
name: "Convertible", id: "G2", expanded: true, children: [
{name: "MINI Cooper", id: "A"},
{name: "BMW Z4", id: "B"},
{name: "Ford Mustang", id: "C"},
{name: "Mercedes-Benz SL", id: "D"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", id: "E"},
{name: "Jeep Wrangler", id: "F"},
{name: "Range Rover", id: "G"},
]
},
]}
/>
);
};
export default Scheduler;
This is an example of a React Scheduler component with statically-defined resources. In a typical React application, you will need to load the resource data from the server side using an HTTP call.
The following example uses the resources
state variable to define and load resources.
The
useEffect()
block watches for thescheduler
state variable change and when the React Scheduler component reference becomes available, it executes theloadData()
method.The
loadData()
function defines an array ofresources
which we load into the Scheduler component using thesetResource()
function. Depending on the specific use case, these resources could represent various entities such as rooms, machines, employees, or any other entity that requires task scheduling.
import React, {useState, useRef, useEffect} from 'react';
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";
const Scheduler = () => {
const [resources, setResources] = useState([]);
const [scheduler, setScheduler] = useState(null);
const loadData = (args) => {
const resources = [
{
name: "Convertible", id: "G2", expanded: true, children: [
{name: "MINI Cooper", id: "A"},
{name: "BMW Z4", id: "B"},
{name: "Ford Mustang", id: "C"},
{name: "Mercedes-Benz SL", id: "D"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", id: "E"},
{name: "Jeep Wrangler", id: "F"},
{name: "Range Rover", id: "G"},
]
},
];
setResources(resources);
}
useEffect(() => {
if (scheduler) {
loadData();
}
}, [scheduler]);
return (
<div>
<DayPilotScheduler
treeEnabled={true}
resources={resources}
controlRef={setScheduler}
/>
</div>
);
}
export default Scheduler;
How to Define the Scheduler Timeline Scale and Time Headers
In the default configuration, the time header of the Scheduler displays today (one cell per hour). We will use startDate, days, scale and timeHeaders properties to customize the timeline:
import React from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
return (
<DayPilotScheduler
...
startDate={DayPilot.Date.today().firstDayOfYear()}
days={DayPilot.Date.today().daysInYear()}
scale={"Day"}
timeHeaders={[
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
]}
/>
);
};
export default Scheduler;
Our React Scheduler component now displays the current year, one cell per day.
We can also adjust the grid size using cellWidth and eventHeight properties of the React scheduler component:
<DayPilotScheduler
cellWidth = {50}
eventHeight = {35}
...
/>
The cellWidth
property defines the Scheduler grid cell width in pixels.
The eventHeight
property specifies the height of event boxes. There are no events in our Scheduler yet but the height of the Scheduler rows is automatically adjusted to match the event height.
The React Scheduler component supports all properties from the JavaScript Scheduler API. You can use them to modify the Scheduler appearance.
So far, we have added the configuration properties to the React Scheduler directly as attributes of the <DayPilotScheduler>
tag. This is convenient when you want to change them on the fly and update the Scheduler automatically. To define properties that are not going to change, you can move them a separate config
object:
import React, { useState } from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
const config{
startDate: DayPilot.Date.today().firstDayOfMonth(),
days: DayPilot.Date.today().daysInMonth(),
scale: "Day",
timeHeaders: [
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
],
};
return (
<DayPilotScheduler
{...config}
/>
);
};
export default Scheduler;
To make a property dynamic, you can define a corresponding state variable and use it as the property value.
Here, the startDate
and days
properties are dynamic:
import React, { useState } from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfMonth());
const [days, setDays] = useState(DayPilot.Date.today().daysInMonth());
const config = {
scale: "Day",
timeHeaders: [
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
],
};
return (
<DayPilotScheduler
{...config}
startDate={startDate}
days={days}
/>
);
};
export default Scheduler;
How to Load Reservation Data into the React Scheduler
In order to load events, we will extend the existing loadData()
function which we used to load the Scheduler resources. The update()
method lets us modify multiple properties at once, making the reload more effective.
const loadData = (args) => {
const resources = [
{
name: "Convertible", id: "G2", expanded: true, children: [
{name: "MINI Cooper", id: "A"},
{name: "BMW Z4", id: "B"},
{name: "Ford Mustang", id: "C"},
{name: "Mercedes-Benz SL", id: "D"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", id: "E"},
{name: "Jeep Wrangler", id: "F"},
{name: "Range Rover", id: "G"},
]
},
];
setResources(resources);
const events = [
{
id: 101,
text: "Reservation 101",
start: "2025-11-02T00:00:00",
end: "2025-11-05T00:00:00",
resource: "A",
barColor: "#93c47d"
},
{
id: 102,
text: "Reservation 102",
start: "2025-11-06T00:00:00",
end: "2025-11-10T00:00:00",
resource: "A",
barColor: "#ffd966"
},
{
id: 103,
text: "Reservation 103",
start: "2025-11-03T00:00:00",
end: "2025-11-10T00:00:00",
resource: "C",
barColor: "#f6b26b"
},
{
id: 104,
text: "Reservation 104",
start: "2025-11-02T00:00:00",
end: "2025-11-08T00:00:00",
resource: "E",
},
{
id: 106,
text: "Reservation 106",
start: "2025-11-02T00:00:00",
end: "2025-11-07T00:00:00",
resource: "B",
barColor: "#6fa8dc"
}
];
setEvents(events);
};
We have defined the events statically using a simple events
array. The items of the events array need to follow the structure defined for DayPilot.Event.data property.
In a real-world application, you can load the data from an API endpoint instead (use two parallel requests and wait for both of them to complete):
const loadData = async () => {
try {
const [resourcesResponse, eventsResponse] = await Promise.all([
fetch('/api/resources'),
fetch('/api/events'),
]);
// Check that the responses are ok
if (!resourcesResponse.ok || !eventsResponse.ok) {
throw new Error('HTTP error, status = ' + resourcesResponse.status);
}
// Convert the response body to JSON
const resourceData = await resourcesResponse.json();
const eventData = await eventsResponse.json();
setResources(resourceData);
setEvents(eventData);
} catch (error) {
console.error('Failed to fetch resources or events:', error);
}
};
How to Show Additional Row Header Columns
In this step, we will extend the Scheduler component to display the following columns in the row header:
Car
Seats
Doors
Transmission
You can define the row header columns using the rowHeaderColumns
property. Each item defines the column name that will be displayed at the top (name), the property name that holds the resource data to be displayed in the column (display
), and the initial column width (width
).
const config = {
rowHeaderColumns: [
{name: "Car"},
{name: "Seats", display: "seats", width: 50},
{name: "Doors", display: "doors", width: 50},
{name: "Transmission", display: "transmission", width: 90},
],
// ...
};
We will customize the row header manually and display an icon for each resource:
const config = {
rowHeaderColumns: [
{name: "Car"},
{name: "Seats", display: "seats", width: 50},
{name: "Doors", display: "doors", width: 50},
{name: "Transmission", display: "transmission", width: 90},
],
onBeforeRowHeaderRender: args => {
if (args.row.data.image) {
args.row.columns[0].areas = [
{
left: 10,
top: 8,
width: 24,
height: 24,
image: "cars/" + args.row.data.image,
style: "border-radius: 50%; overflow: hidden;"
}
]
}
},
// ...
};
Here you can see the updated Scheduler configuration and the resource
array with additional data (car parameters and icon).
import React, {useState, useRef, useEffect} from 'react';
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";
const Scheduler = () => {
const [resources, setResources] = useState([]);
const [events, setEvents] = useState([]);
const [startDate, setStartDate] = useState("2025-01-01");
const [days, setDays] = useState(365);
const [scheduler, setScheduler] = useState(null);
const config = {
rowHeaderColumns: [
{name: "Car"},
{name: "Seats", display: "seats", width: 50},
{name: "Doors", display: "doors", width: 50},
{name: "Transmission", display: "transmission", width: 90},
],
onBeforeRowHeaderRender: args => {
if (args.row.data.image) {
args.row.columns[0].areas = [
{
left: 10,
top: 8,
width: 24,
height: 24,
image: "cars/" + args.row.data.image,
style: "border-radius: 50%; overflow: hidden;"
}
]
}
},
// ...
};
const loadData = (args) => {
const resources = [
{
name: "Convertible", id: "G2", expanded: true, children: [
{name: "MINI Cooper", seats: 4, doors: 2, transmission: "Automatic", id: "A", image: "Mini_Cooper_Icon_24x24.png"},
{name: "BMW Z4", seats: 4, doors: 2, transmission: "Automatic", id: "B", image: "BMW_Z4_icon_24x24.png"},
{name: "Ford Mustang", seats: 4, doors: 2, transmission: "Automatic", id: "C", image: "Ford_Mustang_Icon_24x24.png"},
{name: "Mercedes-Benz SL", seats: 2, doors: 2, transmission: "Automatic", id: "D", image: "Mercedes_Benz_SL_Icon_24x24.png"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", seats: 5, doors: 4, transmission: "Automatic", id: "E", image: "BMW_X1_Icon_24x24.png"},
{name: "Jeep Wrangler", seats: 5, doors: 4, transmission: "Automatic", id: "F", image: "Jeep_Wrangler_Icon_24x24.png"},
{name: "Range Rover", seats: 5, doors: 4, transmission: "Automatic", id: "G", image: "Range_Rover_Icon_24x24.png"},
]
},
];
setResources(resources);
// ...
}
useEffect(() => {
loadData();
}, [scheduler]);
return (
<div>
<DayPilotScheduler
{...config}
startDate={startDate}
days={days}
events={events}
resources={resources}
controlRef={setScheduler}
/>
</div>
);
}
export default Scheduler;
How to Handle User Events (Drag and Drop)
The Scheduler defines event handlers that you can use to get notifications of user changes. Let's see how it works for drag and drop event moving.
Simply define a callback method that receives an args
object as a parameter. The args
object holds details about the drag and drop action and you can use it to notify the server about the change:
<DayPilotScheduler
// ...
onEventMoved={args => {
console.log("Event moved: ", args.e.data.id, args.newStart, args.newEnd, args.newResource);
}}
/>
How to Resize Scheduler Events using Drag and Drop
You can handle drag and drop event resizing using the same mechanism. The Scheduler events/reservations can be resized by dragging either the start or end. As soon as the user finishes the resizing the Scheduler fires onEventResized
event.
The resizing is enabled by default (eventResizeHandling is set to "Update"
) and all we need to do is to add a new onEventResized event handler:
<DayPilotScheduler
// ...
onEventResized={args => {
console.log("Event resized: ", args.e.data.id, args.newStart, args.newEnd);
scheduler.message("Event resized: " + args.e.data.text);
}}
/>
How to Add New Events/Reservations
To add support for creating new events, we need to handle onTimeRangeSelected event handler:
<DayPilotScheduler
// ...
onTimeRangeSelected={async (args) => {
const modal = await DayPilot.Modal.prompt("New event name", "Event");
scheduler.clearSelection();
if (modal.canceled) {
return;
}
scheduler.events.add({
id: DayPilot.guid(),
text: modal.result,
start: args.start,
end: args.end,
resource: args.resource
});
}}
/>
The event handler will open a simple prompt using the built-in DayPilot.Modal dialog and ask for the event name. When the user hits OK, we create the event using the direct API - events.add() method.
How to Display a Message to the User using React Scheduler API
The Scheduler component offers a rich API which you can use in your application. The Scheduler methods let you use advanced features that can't be controlled by properties.
The following example uses message() method to display a message at the top of the Scheduler. It fades away after a couple of seconds and it can be used to provide feedback to users.
As we mentioned before, we need to get a reference to the DayPilot.Scheduler
object to call its methods:
const scheduler = useState();
// ...
return (
<DayPilotScheduler
// ...
controlRef={setScheduler}
/>
);
Now we can use scheduler
to invoke the Scheduler methods directly:
<DayPilotScheduler
// ...
onEventMoved={args => scheduler.message("Event moved: " + args.e.data.text)}
// ...
/>
How to Customize the Color of Reservations
The React Scheduler component is very customizable and it lets you change the event color easily.
By default, the events are white and display a colored bar at the top. If you want to hide the bar, you need to add durationBarVisible
property to the config:
const config{
durationBarVisible: false,
// ...
};
To change the event color depending on the event type, you need to use onBeforeEventRender
event handler. This event is fired for every event/reservation when it is loaded.
onBeforeEventRender: args => {
if (!args.data.backColor) {
args.data.backColor = "#93c47d";
}
args.data.borderColor = "darker";
args.data.fontColor = "white";
}
The "darker"
value of the borderColor
property is a built-in helper that will calculate a darker color from backColor
value automatically.
Full Source Code of Scheduler.js
Here is the full source code of our React Scheduler component:
import React, {useState, useRef, useEffect} from 'react';
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";
const Scheduler = () => {
const [resources, setResources] = useState([]);
const [events, setEvents] = useState([]);
const [startDate, setStartDate] = useState("2025-01-01");
const [days, setDays] = useState(365);
const [scheduler, setScheduler] = useState(null);
const config = {
timeHeaders: [
{groupBy: "Month"},
{groupBy: "Day", format: "d"}
],
scale: "Day",
cellWidth: 50,
durationBarVisible: false,
rowMarginTop: 3,
rowMarginBottom: 3,
treeEnabled: true,
rowHeaderColumns: [
{name: "Car"},
{name: "Seats", display: "seats", width: 50},
{name: "Doors", display: "doors", width: 50},
{name: "Transmission", display: "transmission", width: 90},
],
onEventMoved: args => {
console.log("Event moved: ", args.e.data.id, args.newStart, args.newEnd, args.newResource);
scheduler.message("Event moved: " + args.e.data.text);
},
onEventResized: args => {
console.log("Event resized: ", args.e.data.id, args.newStart, args.newEnd);
scheduler.message("Event resized: " + args.e.data.text);
},
onTimeRangeSelected: async args => {
const modal = await DayPilot.Modal.prompt("New event name", "Event");
scheduler.clearSelection();
if (!modal.result) {
return;
}
scheduler.events.add({
id: DayPilot.guid(),
text: modal.result,
start: args.start,
end: args.end,
resource: args.resource
});
},
onBeforeRowHeaderRender: args => {
if (args.row.data.image) {
args.row.columns[0].areas = [
{
left: 10,
top: 8,
width: 24,
height: 24,
image: "cars/" + args.row.data.image,
style: "border-radius: 50%; overflow: hidden;"
}
]
}
},
onBeforeEventRender: args => {
if (!args.data.backColor) {
args.data.backColor = "#93c47d";
}
args.data.borderColor = "darker";
args.data.fontColor = "white";
args.data.areas = [
{
right: 4,
top: 6,
width: 24,
height: 24,
padding: 2,
fontColor: "#ffffff",
symbol: "icons/daypilot.svg#threedots-v",
action: "ContextMenu",
style: "border-radius: 50%; cursor: pointer;",
}
];
if (args.data.locked) {
args.data.areas.push({
right: 26,
top: 6,
width: 24,
height: 24,
padding: 4,
fontColor: "#ffffff",
symbol: "icons/daypilot.svg#padlock",
style: "border-radius: 50%",
onClick: async args => {
const modal = await DayPilot.Modal.confirm("Do you really want to unlock this reservation?");
if (modal.canceled) {
return;
}
toggleEventLock(args.source);
}
});
args.data.moveDisabled = true;
args.data.resizeDisabled = true;
args.data.clickDisabled = true;
args.data.deleteDisabled = true;
}
},
onRowFilter: args => {
if (!args.filter) {
return;
}
if (args.filterParam.seats) {
args.visible = args.row.data.seats === args.filterParam.seats;
}
},
contextMenu: new DayPilot.Menu({
items: [
{
text: "Delete",
onClick: async args => {
const modal = await DayPilot.Modal.confirm("Do you really want to delete this event?");
if (modal.canceled) {
return;
}
scheduler.events.remove(args.source);
}
},
{
text: "Lock",
onClick: args => {
toggleEventLock(args.source);
}
}
],
onShow: args => {
const e = args.source;
const locked = e.data.locked;
// update the lock/unlock text
args.menu.items[1].text = locked ? "Unlock" : "Lock";
// disable actions for locked
args.menu.items[0].disabled = locked;
}
})
};
const toggleEventLock = (e) => {
e.data.locked = !e.data.locked;
scheduler.events.update(e);
};
const applyFilter = (value) => {
const seats = parseInt(value, 10);
if (!seats) {
scheduler.rows.filter(null);
return;
}
scheduler.rows.filter({seats: seats});
};
const loadData = (args) => {
const resources = [
{
name: "Convertible", id: "G2", expanded: true, children: [
{name: "MINI Cooper", seats: 4, doors: 2, transmission: "Automatic", id: "A", image: "Mini_Cooper_Icon_24x24.png"},
{name: "BMW Z4", seats: 4, doors: 2, transmission: "Automatic", id: "B", image: "BMW_Z4_icon_24x24.png"},
{name: "Ford Mustang", seats: 4, doors: 2, transmission: "Automatic", id: "C", image: "Ford_Mustang_Icon_24x24.png"},
{name: "Mercedes-Benz SL", seats: 2, doors: 2, transmission: "Automatic", id: "D", image: "Mercedes_Benz_SL_Icon_24x24.png"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", seats: 5, doors: 4, transmission: "Automatic", id: "E", image: "BMW_X1_Icon_24x24.png"},
{name: "Jeep Wrangler", seats: 5, doors: 4, transmission: "Automatic", id: "F", image: "Jeep_Wrangler_Icon_24x24.png"},
{name: "Range Rover", seats: 5, doors: 4, transmission: "Automatic", id: "G", image: "Range_Rover_Icon_24x24.png"},
]
},
];
setResources(resources);
const events = [
{
id: 101,
text: "Reservation 101",
start: "2025-11-02T00:00:00",
end: "2025-11-05T00:00:00",
resource: "A"
},
{
id: 102,
text: "Reservation 102",
start: "2025-11-06T00:00:00",
end: "2025-11-10T00:00:00",
resource: "A"
},
{
id: 103,
text: "Reservation 103",
start: "2025-11-03T00:00:00",
end: "2025-11-10T00:00:00",
resource: "C",
backColor: "#6fa8dc",
locked: true
},
{
id: 104,
text: "Reservation 104",
start: "2025-11-02T00:00:00",
end: "2025-11-08T00:00:00",
resource: "E",
backColor: "#f6b26b",
plus: true
},
{
id: 105,
text: "Reservation 105",
start: "2025-11-03T00:00:00",
end: "2025-11-09T00:00:00",
resource: "G",
},
{
id: 106,
text: "Reservation 106",
start: "2025-11-02T00:00:00",
end: "2025-11-07T00:00:00",
resource: "B",
}
];
setEvents(events);
}
useEffect(() => {
loadData();
scheduler?.scrollTo("2025-11-01");
}, [scheduler]);
return (
<div>
<div className="toolbar">
Filter by seats:
<select onChange={ev => applyFilter(ev.currentTarget.value)}>
<option value="0">All</option>
<option value="2">2</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
<DayPilotScheduler
{...config}
startDate={startDate}
days={days}
events={events}
resources={resources}
controlRef={setScheduler}
/>
</div>
);
}
export default Scheduler;
History
July 17, 2024: Upgraded to DayPilot Pro 2024.3.5973. Added: row header column icons, row filter, event context menu.
May 26, 2023: Converted to a functional React component (Hooks API), React 18, DayPilot Pro for JavaScript 2023.2.5582.
June 21, 2021: Row columns added, how to change event color.
May 20, 2021: Upgraded to React 17, DayPilot Pro for JavaScript 2021.2.4990.
November 22, 2020: Upgraded to DayPilot Pro for JavaScript 2020.4.4766.
September 11, 2019: Upgraded to DayPilot Pro for JavaScript 2019.3.4000, toolbar styling, event creating, event resizing.
May 21, 2018: Component interop (zoom component), custom event bar color.
May 18, 2018: Initial version released.