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

React Scheduler Component Configurator

To get started more easily, you can use the online UI Builder app to configure the React Scheduler component:

  1. Configure the component using the available preset values.

  2. Preview your changes immediately with a live Scheduler instance.

  3. Download a complete React project that includes your selected configuration.

Create a React Scheduler Component using 6 Lines of Code

react scheduler component initialization

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

React Scheduler Component - Loading Resources

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.

  1. The useEffect() block watches for the scheduler state variable change and when the React Scheduler component reference becomes available, it executes the loadData() method.

  2. The loadData() function defines an array of resources which we load into the Scheduler component using the setResource() 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

React Scheduler Component - Timeline Scale and Time Headers

In the default configuration, the time header of the Scheduler displays today (one cell per hour). We will use startDatedaysscale 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.

React Scheduler Component - Grid Cell Size

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

React Scheduler Component - Loading Reservation Data

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

React Scheduler Component - 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)

React Scheduler Component - Handling User Events

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

React Scheduler Component - Resizing Reservations 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

React Scheduler Component - Adding 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.

React Scheduler Component - New Reservation Dialog

How to Display a Message to the User using React Scheduler API

React Scheduler Component - Display a Message

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

React Scheduler Component - Customizing Reservation Appearance

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:
        &nbsp;
        <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.