Overview

  • React application that shows a resource scheduling calendar (with custom resources as columns).

  • Learn how to load resources (columns) and events using the React state variables.

  • The columns displayed by the React calendar can be edited using a modal dialog that opens after clicking the column header.

  • Use a drop-down list to select a group of resources that will be displayed in the calendar.

  • Change the date using a date picker or Next/Previous buttons (both methods work together).

  • Built using the free and open-source React calendar component from DayPilot Lite for JavaScript.

License

Apache License 2.0

How to Get Started with the React Resource Calendar

react resource calendar open source getting started

In this step, you will learn how to add the resource calendar to your React application.

Create a new React application and install the @daypilot/daypilot-lite-react package from NPM. This package includes the required calendar/scheduler components and the date picker.

npm install @daypilot/daypilot-lite-react

Create a new Calendar component (Calendar.js file) and add a <DayPilotCalendar> tag to the JSX:

import React, { useState, useEffect } from 'react';
import { DayPilotCalendar } from "@daypilot/daypilot-lite-react";

const Calendar = () => {
  // State for viewType
  const [config, setConfig] = useState({
    viewType: "Resources",
    columns: [
      { name: "Room 1", id: "R1" },
      { name: "Room 2", id: "R2" },
      { name: "Room 3", id: "R3" },
      { name: "Room 4", id: "R4" },
      { name: "Room 5", id: "R5" },
      { name: "Room 6", id: "R6" },
      { name: "Room 7", id: "R7" },
    ]
  });

  // State for startDate
  const [startDate, setStartDate] = useState(DayPilot.Date.today());

  return (
    <DayPilotCalendar
      {...config}
      startDate={startDate}
    />
  );
}

export default Calendar;

Lets go through the important elements of this example:

1. By default, the React calendar component displays a daily calendar view. In order to switch to the resources calendar view (with resources as columns), add viewType={"Resources"} to the configuration.

<DayPilotCalendar
  viewType={"Resources"}
/>

2. In order to make the configuration easier (and changeable), move the calendar properties to the config state variable and use the spread operator (…) to inject the properties as <DayPilotCalendar> attributes.

const [config, setConfig] = useState({
  viewType: "Resources",
});

return (
  <DayPilotCalendar
    {...config}
  />
);

3. To set the initial date displayed by the calendar, create a startDate state variable.

const [startDate, setStartDate] = useState(DayPilot.Date.today());

// ...

return (
  <DayPilotCalendar
    {...config}
    startDate={startDate}
  />
);

How to Load Calendar Resources and Events

Open Source React Resource Calendar   Load Resources and Events

In this step, we will move the resource data (columns) to a special state variable and load the data during the initial render.

To define the resources, create a columns state variable. Create a new useEffect() block, with [] as the dependency list, and use setColumns() to define the column array.

const [columns, setColumns] = useState([]);

useEffect(() => {
  setColumns([
    { name: "Room 1", id: "R1" },
    { name: "Room 2", id: "R2" },
    { name: "Room 3", id: "R3" },
    { name: "Room 4", id: "R4" },
    { name: "Room 5", id: "R5" },
    { name: "Room 6", id: "R6" },
    { name: "Room 7", id: "R7" },
  ]);
}, []);

To load events, use the events state variable:

const [events, setEvents] = useState([]);

useEffect(() => {
  setEvents([
    {
      id: 1,
      text: "Event 1",
      start: "2024-11-07T11:00:00",
      end: "2024-11-07T13:30:00",
      barColor: "#fcb711",
      resource: "R1"
    },
    {
      id: 2,
      text: "Event 2",
      start: "2024-11-07T10:00:00",
      end: "2024-11-07T12:00:00",
      barColor: "#f37021",
      resource: "R2"
    },
    // ...
  ]);
}, []);

The structure of the events and columns arrays is described in the API documentation.

This is how our React calendar component looks now:

import React, { useState, useEffect } from 'react';
import { DayPilotCalendar } from "@daypilot/daypilot-lite-react";

const Calendar = () => {
  // State for viewType
  const [config, setConfig] = useState({
    viewType: "Resources",
  });

  // State for startDate
  const [startDate, setStartDate] = useState(DayPilot.Date.today());

  // State for columns
  const [columns, setColumns] = useState([]);

  // State for calendar events/reservations
  const [events, setEvents] = useState([]);

  // useEffect to initialize columns
  useEffect(() => {
    setColumns([
      { name: "Room 1", id: "R1" },
      { name: "Room 2", id: "R2" },
      { name: "Room 3", id: "R3" },
      { name: "Room 4", id: "R4" },
      { name: "Room 5", id: "R5" },
      { name: "Room 6", id: "R6" },
      { name: "Room 7", id: "R7" },
    ]);

    setEvents([
      {
        id: 1,
        text: "Event 1",
        start: "2024-11-07T11:00:00",
        end: "2024-11-07T13:30:00",
        barColor: "#fcb711",
        resource: "R1"
      },
      {
        id: 2,
        text: "Event 2",
        start: "2024-11-07T10:00:00",
        end: "2024-11-07T12:00:00",
        barColor: "#f37021",
        resource: "R2"
      },
      // ...
    ]);

  }, []);

  return (
    <DayPilotCalendar
      {...config}
      startDate={startDate}
      columns={columns}
      events={events}
    />
  );
}

export default Calendar;

Now we have the basic React resource calendar working. In the next chapter, you will see how to load the resources and events from an external source and add rich UI that lets users switch the calendar views.

How to Use the Direct API to Reach Resource Calendar Features

The reactive model we used so far lets us modify the calendar by simply updating the state. This is a simple approach that works fine in most cases.

In the next step, we will see how to use the direct API instead of state variables to update the Calendar.

  • This approach gives you more precise control over the changes - you specify what gets updated and when.

  • In more complex scenarios, the direct API access makes is easier to manage state shared by multiple components.

  • The DayPilot.Calendar class provides additional methods that can help you perform additional actions (find an event, get the visible date range, clear the time range selection, etc.).

To access the direct API, we need a reference to the DayPilot.Calendar instance that represents the Calendar component.

import React, { useState, useRef } from 'react';
import { DayPilot, DayPilotCalendar } from "@daypilot/daypilot-lite-react";

const Calendar = () => {
  const calendarRef = useRef(null);

  // Replace '...' with your actual config initialization
  const [config, setConfig] = useState({
    // ...
  });

  // This function acts as a getter for the calendar control
  const calendar = () => {
    return calendarRef.current ? calendarRef.current.control : null;
  };

  return (
    <DayPilotCalendar
      {...config}
      ref={calendarRef}
    />
  );
}

export default Calendar;

The calendar() method returns the DayPilot.Calendar object from the calendar component reference stored in calendarRef.

Now we can use calendar() to call the calendar component methods. In the next chapter, we will use it to update events and columns.

How to Edit Resource Calendar Columns

Open Source React Resource Calendar Header Column Edit Icon

Our React application will let users edit column details on click. To implement this feature, we will add an “Edit” icon to the column header using the onBeforeHeaderRender event handler.

The onBeforeHeaderRender event handler lets you customize the resource header:

  • modify the text

  • set custom HTML

  • change the background color

  • insert icons

  • add action buttons

We will use it to add an icon as an active area. It will show an “Edit” icon from the built-in daypilot.svg icon bundle and use the onClick event to show a modal dialog with column details.

const onBeforeHeaderRender = args => {
  args.header.areas = [
    {
      right: "5",
      top: 5,
      width: 30,
      height: 30,
      // fontColor: "#999999",
      symbol: "/daypilot.svg#edit",
      padding: 6,
      cssClass: "icon",
      toolTip: "Edit...",
      onClick: async args => {
        const column = args.source;
        const modal = await DayPilot.Modal.prompt("Resource name:", column.name);
        if (!modal.result) { return; }
        column.data.name = modal.result;
        setColumns([...columns]);
      }
    }
  ];
};


return (
  <DayPilotCalendar
    onBeforeHeaderRender={onBeforeHeaderRender}
  />
);

The icon CSS class defines the appearance and hover effect:

.icon {
    box-sizing: border-box;
    border-radius: 50%;
    overflow: hidden;
    cursor: pointer;
    color: #ffffff;
    background-color: #777777;
    transition: all 0.3s;
}

.icon:hover {
    background-color: #3c78d8;
}

The onClick event handler opens a simple modal dialog that asks for an updated resource name:

react resource calendar column name edit dialog

As soon as the user closes the dialog, we update the column name (the column.data object stores the original item from the columns array):

column.data.name = modal.result;

Then we notify React that the events need to be reloaded:

setColumns([...columns]);

How to Switch Between Different Resource Groups

Open Source React Resource Calendar Select Resource Group

To switch between different resource groups, we will create a ResourceGroups React component that displays a drop-down list with all available groups.

import React, { useState, useEffect, useRef, useCallback } from 'react';

export function ResourceGroups({ items, onChange }) {
  const initialSelectedValue = items && items.length > 0 ? items[0].id : '';
  const [selectedValue, setSelectedValue] = useState(initialSelectedValue);
  const selectRef = useRef(null);

  const doOnChange = useCallback((item) => {
    const args = { selected: item };
    if (onChange) {
      onChange(args);
    }
  }, [onChange]); // Dependency on onChange

  useEffect(() => {
    if (items && items.length > 0 && selectedValue === '') {
      const firstItemId = items[0].id;
      setSelectedValue(firstItemId);
      doOnChange(items[0]);
    }
  }, [items, selectedValue, doOnChange]); // Include doOnChange in the dependencies

  const find = (id) => {
    return items ? items.find(item => item.id === id) : null;
  };

  const change = (ev) => {
    const value = ev.target.value;
    setSelectedValue(value);
    const item = find(value);
    doOnChange(item);
  };

  return (
    <span>
      Group: &nbsp;
      <select onChange={change} ref={selectRef} value={selectedValue}>
        {items.map(item => (
          <option key={item.id} value={item.id}>{item.name}</option>
        ))}
      </select>
    </span>
  );
}

Now we can add the drop-down component to Calendar.js (just above the resource calendar):

<ResourceGroups onChange={args => setSelected(args.selected)} items={groups}></ResourceGroups>

You can see that the ResourceGroups component reads the data from the items prop and fires the onChange event when the selection changes.

The groups state variable contains the list of groups and their children (resources). The selected group is stored in the selected state variable:

const [groups, setGroups] = useState([]);
const [selected, setSelected] = useState();

useEffect(() => {

  const data = [
    { name: "Locations", id: "locations", resources: [
        {name: "Room 1", id: "R1"},
        {name: "Room 2", id: "R2"},
        {name: "Room 3", id: "R3"},
        {name: "Room 4", id: "R4"},
        {name: "Room 5", id: "R5"},
        {name: "Room 6", id: "R6"},
        {name: "Room 7", id: "R7"},
      ]
    },
    { name: "People", id: "people", resources: [
        {name: "Person 1", id: "P1"},
        {name: "Person 2", id: "P2"},
        {name: "Person 3", id: "P3"},
        {name: "Person 4", id: "P4"},
        {name: "Person 5", id: "P5"},
        {name: "Person 6", id: "P6"},
        {name: "Person 7", id: "P7"},
      ]
    },
    { name: "Tools", id: "tools", resources: [
        {name: "Tool 1", id: "T1"},
        {name: "Tool 2", id: "T2"},
        {name: "Tool 3", id: "T3"},
        {name: "Tool 4", id: "T4"},
        {name: "Tool 5", id: "T5"},
        {name: "Tool 6", id: "T6"},
        {name: "Tool 7", id: "T7"},
      ]
    },
  ];

  setGroups(data);
  setSelected(data[0]);

}, []);

The column loading logic needs to be updated. Instead of loading a static set of resource during the initial render, we need to detect a change of the selected group and load the group children.

To do this, we will use a useEffect() block that uses the selected variable as a dependency:

useEffect(() => {
  setColumns(selected?.resources || []);
}, [selected, groups]);

In the next step, we will load events. The event data set depends on the selected date (startDate) and the visible resources (columns).

useEffect(() => {
  // load events for the visible date (`startDate`) and resources in the selected group (`selected`)
  setEvents([
    {
      id: 1,
      text: "Event 1",
      start: "2024-11-07T10:30:00",
      end: "2024-11-07T13:00:00",
      barColor: "#fcb711",
      resource: "R1"
    },
    // ...
  ]);
}, [startDate, columns]);

For the sake of simplicity, this function uses a static array of events (instead of loading them using an HTTP call from a server) and doesn’t filter them (it includes events for resources that are not visible at the moment).

How to Change the Resource Calendar Date

Open Source React Resource Calendar Change Date

The user interface of this React application provides two ways to change the date:

  • A date picker displayed on the left side shows a small calendar with 3 months.

  • “Next” and “Previous” buttons displayed above the calendar.

To show the date picker, use the Navigator component (<DayPilotNavigator> tag):

<DayPilotNavigator
  selectMode={"Day"}
  showMonths={3}
  skipMonths={3}
  selectionDay={startDate}
  startDate={startDate}
  onTimeRangeSelected={ args => setStartDate(args.day) }
/>

The Navigator fires onTimeRangeSelected event when the selected date changes. Here, the event handler updates the startDate state variable. React detects this change and updates the resource calendar.

The selection date can be set using the selectionDay attribute:

selectionDay={startDate}

The startDate attribute sets the first visible month. In this case, we will link it to the startDate state variable as well:

startDate={startDate}

The “Next” and “Previous” buttons use the onClick event to change the date:

<button onClick={ev => previous()}>Previous</button>
<button onClick={ev => next()}>Next</button>

The previous() method selects a new date using the setStartDate() function. This updates both components - the date picker and the calendar.

const previous = () => {
  setStartDate(startDate.addDays(-1));
};

The next() method is very similar but it adds one day instead of subtracting it:

const next = () => {
  setStartDate(startDate.addDays(1));
};

History

  • November 12, 2023: Converted to the React Hooks API and functional components; the logic is now fully reactive. DayPilot Lite has been upgraded to version 2023.3.499 (3.18.0).

  • July 19, 2022: Initial version