What You Will Learn in This Tutorial
How to add a React Scheduler component to your application
How to display monthly and weekly scheduler and switch between the views using zoom
How to load reservation data
How to change event/reservation color
How to enable drag and drop operations (moving, resizing)
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/2023.2.5582.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
resources={[
{name: "Resource A", id: "A"},
{name: "Resource B", id: "B"},
{name: "Resource C", id: "C"},
{name: "Resource D", id: "D"},
{name: "Resource E", id: "E"},
{name: "Resource F", id: "F"},
{name: "Resource G", 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 direct Scheduler API to load resources and display resources as rows.
First, it gets a reference to Scheduler component using the
ref
attribute of the<DayPilotScheduler>
tag. It stores the component reference in theschedulerRef
mutable object, initialized using theuseRef()
method.Now we can access the underlying DayPilot.Scheduler object as
schedulerRef.current.control
.The
loadData()
function defines an array ofresources
which we load into the Scheduler component using the update() method. 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.
A useEffect
hook is used to call loadData
function after the component has been rendered. The empty dependency array ([]
) passed to useEffect
ensures that this side effect runs once and only once, after the initial render of the component.
import React from 'react';
import { DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
const schedulerRef = useRef();
const loadData = (args) => {
const resources = [
{name: "Resource A", id: "A"},
{name: "Resource B", id: "B"},
{name: "Resource C", id: "C"},
{name: "Resource D", id: "D"},
{name: "Resource E", id: "E"},
{name: "Resource F", id: "F"},
{name: "Resource G", id: "G"}
];
schedulerRef.current.control.update({
resources
});
}
useEffect(() => {
loadData();
}, []);
return (
<DayPilotScheduler
ref={schedulerRef}
/>
);
};
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().firstDayOfMonth()}
days={DayPilot.Date.today().daysInMonth()}
scale={"Day"}
timeHeaders={[
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
]}
/>
);
};
export default Scheduler;
Our React Scheduler component now displays the current month, 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 using a small set of configuration properties. If you want to define more properties and change them dynamically, it is better to move them to a separate config
state variable:
import React, { useState } from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
const Scheduler = () => {
const [config, setConfig] = useState({
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;
By using React's useState
hook, the config
object is made reactive - changes to this state will trigger re-render of the component. This pattern allows for more efficient and organized code, especially as the complexity of the component increases.
Just remember, whenever you want to change the value of the config
object, you should use the setConfig
function to ensure React's state is updated correctly and the component is re-rendered with the new configuration.
If you want to change a selected property, use the following approach:
setConfig({
...config,
startDate: config.startDate.addDays(1)
});
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", seats: 4, doors: 2, transmission: "Automatic", id: "A"},
{name: "BMW Z4", seats: 4, doors: 2, transmission: "Automatic", id: "B"},
{name: "Ford Mustang", seats: 4, doors: 2, transmission: "Automatic", id: "C"},
{name: "Mercedes-Benz SL", seats: 2, doors: 2, transmission: "Automatic", id: "D"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", seats: 5, doors: 4, transmission: "Automatic", id: "E"},
{name: "Jeep Wrangler", seats: 5, doors: 4, transmission: "Automatic", id: "F"},
{name: "Range Rover", seats: 5, doors: 4, transmission: "Automatic", id: "G"},
]
},
];
const events = [
{id: 101, text: "Reservation 101", start: "2023-11-02T00:00:00", end: "2023-11-05T00:00:00", resource: "A"},
{id: 102, text: "Reservation 102", start: "2023-11-06T00:00:00", end: "2023-11-10T00:00:00", resource: "A"},
{
id: 103,
text: "Reservation 103",
start: "2023-11-03T00:00:00",
end: "2023-11-10T00:00:00",
resource: "C",
backColor: "#6fa8dc",
locked: true
},
{
id: 104,
text: "Reservation 104",
start: "2023-11-02T00:00:00",
end: "2023-11-08T00:00:00",
resource: "E",
backColor: "#f6b26b",
plus: true
},
{
id: 105,
text: "Reservation 105",
start: "2023-11-03T00:00:00",
end: "2023-11-09T00:00:00",
resource: "G",
},
{
id: 106,
text: "Reservation 106",
start: "2023-11-02T00:00:00",
end: "2023-11-07T00:00:00",
resource: "B",
}
];
getScheduler().update({
resources,
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 resources = await resourcesResponse.json();
const events = await eventsResponse.json();
// Update the scheduler
setConfig((prevConfig) => ({
...prevConfig,
resources,
events,
}));
} catch (error) {
console.error('Failed to fetch resources or events:', error);
}
};
How to Switch Views using React Scheduler Zoom
Now that we have defined the Scheduler properties using a special state object we can use the React automatic change detection mechanism to change the Scheduler appearance dynamically.
Let's create a simple Zoom component using radio buttons that will let users choose the desired zoom level of the Scheduler:
scheduler/Zoom.js
import React, { useState } from 'react';
const Zoom = ({ onChange }) => {
const [level, setLevel] = useState("month");
const handleChange = (ev) => {
const newLevel = ev.target.value;
setLevel(newLevel);
if (onChange) {
onChange({level: newLevel});
}
};
return (
<span className="toolbar-item">
Zoom:
<label><input type="radio" name="zoom" value="month" onChange={handleChange} checked={level === "month"} /> Month</label>
<label><input type="radio" name="zoom" value="week" onChange={handleChange} checked={level === "week"} /> Week</label>
</span>
);
}
export default Zoom;
The Zoom component displays radio buttons group with two zoom level options:
Month
Week
Whenever the user selects a new value the internal component state is updated and the parent component is notified using a custom onChange callback. The onChange
argument holds the newly selected zoom level:
args.level
("month"
|"week"
)
Now we can add our new Zoom component to our application. We will listen to zoom level changes by defining an onChange
event handler. Our event handler updates the required properties of the config
object. The Scheduler component detects a state change and updates automatically.
import React, { useState } from 'react';
import { DayPilot, DayPilotScheduler } from "daypilot-pro-react";
import Zoom from "./Zoom";
const Scheduler = () => {
const [config, setConfig] = useState({
startDate: "2023-10-01",
days: 31,
scale: "Day",
timeHeaders: [
{ groupBy: "Month" },
{ groupBy: "Day", format: "d" }
],
// ...
});
const zoomChange = (args) => {
switch (args.level) {
case "month":
setConfig({
...config,
startDate: DayPilot.Date.today().firstDayOfMonth(),
days: DayPilot.Date.today().daysInMonth(),
scale: "Day"
});
break;
case "week":
setConfig({
...config,
startDate: DayPilot.Date.today().firstDayOfWeek(),
days: 7,
scale: "Day"
});
break;
default:
throw new Error("Invalid zoom level");
}
}
return (
<div>
<div className="toolbar">
<Zoom onChange={zoomChange} />
</div>
<DayPilotScheduler {...config} />
</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);
schedulerRef.current.control.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");
schedulerRef.current.control.clearSelection();
if (modal.canceled) {
return;
}
schedulerRef.current.control.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 schedulerRef = useRef();
// ...
return (
<DayPilotScheduler
// ...
ref={schedulerRef}
/>
);
Now we can use schedulerRef.current.control
to invoke the Scheduler methods directly:
<DayPilotScheduler
// ...
onEventMoved={args => schedulerRef.current.control.message("Event moved: " + args.e.data.text)}
// ...
/>
To make the access to the DayPilot.Scheduler
object easier, we can add a shortcut function:
const getScheduler = () => schedulerRef.current.control;
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, setConfig] = useState({
durationBarVisible: false,
// ...other fields...
});
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
scheduler/Scheduler.js
import React, {useState, useRef, useEffect} from 'react';
import {DayPilot, DayPilotScheduler} from "daypilot-pro-react";
import Zoom from "./Zoom";
const Scheduler = () => {
const [config, setConfig] = useState({
startDate: "2023-11-01",
days: 31,
scale: "Day",
timeHeaders: [
{groupBy: "Month"},
{groupBy: "Day", format: "d"}
],
cellWidthSpec: "Auto",
cellWidth: 50,
durationBarVisible: false,
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);
getScheduler().message("Event moved: " + args.e.data.text);
},
onEventResized: args => {
console.log("Event resized: ", args.e.data.id, args.newStart, args.newEnd);
getScheduler().message("Event resized: " + args.e.data.text);
},
onTimeRangeSelected: args => {
DayPilot.Modal.prompt("New event name", "Event").then(modal => {
getScheduler().clearSelection();
if (!modal.result) {
return;
}
getScheduler().events.add({
id: DayPilot.guid(),
text: modal.result,
start: args.start,
end: args.end,
resource: args.resource
});
});
},
onBeforeEventRender: args => {
if (!args.data.backColor) {
args.data.backColor = "#93c47d";
}
args.data.borderColor = "darker";
args.data.fontColor = "white";
args.data.areas = [];
if (args.data.locked) {
args.data.areas.push(
{
right: 4,
top: 8,
height: 18,
width: 18,
symbol: "icons/daypilot.svg#padlock",
fontColor: "white"
}
);
} else if (args.data.plus) {
args.data.areas.push(
{
right: 4,
top: 8,
height: 18,
width: 18,
symbol: "icons/daypilot.svg#plus-4",
fontColor: "white"
}
);
}
},
});
const schedulerRef = useRef();
const getScheduler = () => schedulerRef.current.control;
const zoomChange = (args) => {
switch (args.level) {
case "month":
setConfig({
...config,
startDate: DayPilot.Date.today().firstDayOfMonth(),
days: DayPilot.Date.today().daysInMonth(),
scale: "Day"
});
break;
case "week":
setConfig({
...config,
startDate: DayPilot.Date.today().firstDayOfWeek(),
days: 7,
scale: "Day"
});
break;
default:
throw new Error("Invalid zoom level");
}
};
const cellWidthChange = (ev) => {
const checked = ev.target.checked;
setConfig(prevConfig => ({
...prevConfig,
cellWidthSpec: checked ? "Auto" : "Fixed"
}));
};
const loadData = (args) => {
const resources = [
{
name: "Convertible", id: "G2", expanded: true, children: [
{name: "MINI Cooper", seats: 4, doors: 2, transmission: "Automatic", id: "A"},
{name: "BMW Z4", seats: 4, doors: 2, transmission: "Automatic", id: "B"},
{name: "Ford Mustang", seats: 4, doors: 2, transmission: "Automatic", id: "C"},
{name: "Mercedes-Benz SL", seats: 2, doors: 2, transmission: "Automatic", id: "D"},
]
},
{
name: "SUV", id: "G1", expanded: true, children: [
{name: "BMW X1", seats: 5, doors: 4, transmission: "Automatic", id: "E"},
{name: "Jeep Wrangler", seats: 5, doors: 4, transmission: "Automatic", id: "F"},
{name: "Range Rover", seats: 5, doors: 4, transmission: "Automatic", id: "G"},
]
},
];
const events = [
{id: 101, text: "Reservation 101", start: "2023-11-02T00:00:00", end: "2023-11-05T00:00:00", resource: "A"},
{id: 102, text: "Reservation 102", start: "2023-11-06T00:00:00", end: "2023-11-10T00:00:00", resource: "A"},
{
id: 103,
text: "Reservation 103",
start: "2023-11-03T00:00:00",
end: "2023-11-10T00:00:00",
resource: "C",
backColor: "#6fa8dc",
locked: true
},
{
id: 104,
text: "Reservation 104",
start: "2023-11-02T00:00:00",
end: "2023-11-08T00:00:00",
resource: "E",
backColor: "#f6b26b",
plus: true
},
{
id: 105,
text: "Reservation 105",
start: "2023-11-03T00:00:00",
end: "2023-11-09T00:00:00",
resource: "G",
},
{
id: 106,
text: "Reservation 106",
start: "2023-11-02T00:00:00",
end: "2023-11-07T00:00:00",
resource: "B",
}
];
getScheduler().update({
resources,
events
});
}
useEffect(() => {
loadData();
}, []);
return (
<div>
<div className="toolbar">
<Zoom onChange={args => zoomChange(args)}/>
<button onClick={ev => getScheduler().message("Welcome!")}>Welcome!</button>
<span className="toolbar-item"><label><input type="checkbox" checked={config.cellWidthSpec === "Auto"}
onChange={ev => cellWidthChange(ev)}/> Auto width</label></span>
</div>
<DayPilotScheduler
{...config}
ref={schedulerRef}
/>
</div>
);
}
export default Scheduler;
Main React Application Class
App.js
import React from 'react';
import './App.css';
import Scheduler from "./scheduler/Scheduler";
const App = () => {
return (
<Scheduler/>
);
}
export default App;
History
May 26, 2023: Converted to 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.