Overview

  • Search events displayed by the React Scheduler component from DayPilot Pro for JavaScript.

  • Highlight the matching text in all Scheduler events.

  • Focus the event at the current position.

  • “Next” and “Previous” buttons let you move the position to the next or previous matching event.

This tutorial keeps all events visible and highlights the matching text. It is also possible to filter events and hide ones that don’t match the specified rule.

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of this tutorial if you are a licensed user of DayPilot Pro for JavaScript.

How to Create the React Scheduler Component?

react scheduler how to search events initialization

We start with a current React 19 project generated using the DayPilot UI Builder. The application entry point is src/App.jsx, and the Scheduler implementation lives in src/scheduler/Scheduler.jsx.

  • It displays the scheduler grid using the DayPilot React Scheduler component (<DayPilotScheduler>).

  • It uses plain React state for the event list, the search phrase, and the current result position.

  • It generates the timeline for the current month using DayPilot.Date.today().firstDayOfMonth().

The following code listing shows the overall structure of the Scheduler component (some data and helper details are omitted to keep it short).

To learn more about using the React Scheduler component from DayPilot Pro in your React application, please see the React Scheduler Component Tutorial.

import { useState } from 'react';
import { DayPilot, DayPilotScheduler } from 'daypilot-pro-react';
import SearchBar from './SearchBar.jsx';

const startDate = DayPilot.Date.today().firstDayOfMonth();

const Scheduler = () => {
  const [scheduler, setScheduler] = useState(null);
  const [events, setEvents] = useState(createEvents);
  const [phrase, setPhrase] = useState('');
  const [position, setPosition] = useState(0);

  return (
    <div>
      <SearchBar
        phrase={phrase}
        resultCount={results.length}
        position={currentPosition}
        onPhraseChange={handlePhraseChange}
        onPrevious={handlePrevious}
        onNext={handleNext}
      />
      <DayPilotScheduler
        timeHeaders={[{ groupBy: 'Month' }, { groupBy: 'Day', format: 'd' }]}
        scale="Day"
        days={startDate.daysInMonth()}
        startDate={startDate}
        resources={resources}
        events={events}
        controlRef={setScheduler}
      />
    </div>
  );
};

How to Add the Search Functionality to the Scheduler?

Now we can start building the search functionality.

The search toolbar is a small presentational component (SearchBar.jsx). The Scheduler component keeps the search state and passes the current phrase, result counter, and navigation handlers to the toolbar. Typing just part of an event name such as eve is enough to highlight all matching events while you continue typing.

<SearchBar
  phrase={phrase}
  resultCount={results.length}
  position={currentPosition}
  onPhraseChange={handlePhraseChange}
  onPrevious={handlePrevious}
  onNext={handleNext}
/>

react scheduler search component

The Search toolbar (SearchBar.jsx) displays four elements:

  • input box for the search phrase

  • label with the current position in the result set

  • next button

  • previous button

const SearchBar = ({ phrase, resultCount, position, onPhraseChange, onPrevious, onNext }) => {
  const searchActive = normalizePhrase(phrase).length > 0
  const info = searchActive ? `${resultCount === 0 ? 0 : position + 1}/${resultCount}` : ''
  const displayStyle = { display: resultCount > 0 ? '' : 'none' }

  return (
    <div className="space search-bar">
      <label htmlFor="event-search">Search:</label>
      <input id="event-search" value={phrase} onChange={(event) => onPhraseChange(event.target.value)} />
      <span className="result-info">{info}</span>
      <button title="Previous" onClick={onPrevious} style={displayStyle} className="next-previous">
        <svg width={18} height={18}><use href="icons/daypilot.svg#minichevron-up-2" /></svg>
      </button>
      <button title="Next" onClick={onNext} style={displayStyle} className="next-previous">
        <svg width={18} height={18}><use href="icons/daypilot.svg#minichevron-down-2" /></svg>
      </button>
    </div>
  )
}

The React Scheduler API provides methods and events that help with the implementation:

  • events.findAll() searches the loaded Scheduler data and returns matching DayPilot.Event objects.

  • onBeforeEventRender lets us update the event HTML and the focused result styling.

How to Implement the Search Logic?

The search state in the Scheduler component contains two variables:

  • search phrase (phrase)

  • current position in the result set (position)

The matching events are derived from the current data. Before the Scheduler control is initialized, we filter the React event array directly. After initialization, we use events.findAll() to get the matching DayPilot.Event objects and map them to event ids.

let results = [];

if (phrase.trim()) {
  const searchText = phrase.trim().toLowerCase();

  if (scheduler) {
    const matchingEvents = scheduler.events.findAll((event) => {
      return event.data.text.toLowerCase().includes(searchText);
    });

    results = matchingEvents.map((event) => event.data.id);
  }
  else {
    results = events
      .filter((event) => event.text.toLowerCase().includes(searchText))
      .map((event) => event.id);
  }
}

const currentPosition = results.length === 0 ? 0 : Math.min(position, results.length - 1);

Blank input produces an empty result set, which clears the highlighting and hides the navigation buttons. That fixes the legacy behavior where an empty query matched every event.

The input uses the change event handler through React onChange and resets the current position to the first result whenever the phrase changes:

const handlePhraseChange = (value) => {
  setPhrase(value);
  setPosition(0);
};

Whenever the focused result changes, we refresh the rendered events using the direct API (update() with the current events list) and make sure that the focused event is visible using events.scrollIntoView(). Including events in the effect dependencies keeps the active search synchronized when you add another event.

useEffect(() => {
  if (!scheduler) {
    return;
  }

  refreshScheduler(scheduler);

  const searchText = phrase.trim().toLowerCase();
  if (!searchText) {
    return;
  }

  const matchingEvents = scheduler.events.findAll((event) => {
    return event.data.text.toLowerCase().includes(searchText);
  });

  if (matchingEvents.length === 0) {
    return;
  }

  const focusedEvent = matchingEvents[currentPosition];
  if (focusedEvent) {
    scheduler.events.scrollIntoView(focusedEvent);
  }
}, [currentPosition, events, phrase, scheduler]);

When updating the events, the Scheduler calls the onBeforeEventRender event handler for each of them:

  • We update the event HTML (args.data.html) and highlight the search phrase in all events that contain it.

  • We add a .focused CSS class to the event at the current position (args.data.cssClass).

Here is the exact implementation from Scheduler.jsx:

const highlightText = (source, phrase) => {
  const escaped = phrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const regex = new RegExp(escaped, 'ig');

  return source.replace(regex, "<span style='background-color: #ffa500; color: #fff;'>$&</span>");
};

onBeforeEventRender={(args) => {
  args.data.html = args.data.text;
  args.data.cssClass = '';

  const searchText = phrase.trim().toLowerCase();
  if (!searchText) {
    return;
  }

  if (!args.data.text.toLowerCase().includes(searchText)) {
    return;
  }

  args.data.html = highlightText(args.data.text, searchText);

  const focusedEventId = results[currentPosition];
  if (focusedEventId === args.data.id) {
    args.data.cssClass = 'focused';
  }
}}

How to Add “Next” and “Previous” Buttons?

react scheduler how to search events next previous

The “Next” button skips to the next matching event.

<button title="Next" onClick={onNext} style={displayStyle} className="next-previous">
  <svg width={18} height={18}><use href="icons/daypilot.svg#minichevron-down-2" /></svg>
</button>

The button is only displayed if the result set includes at least one event. Otherwise, it is hidden using CSS:

const displayStyle = { display: resultCount > 0 ? '' : 'none' }

The onClick event handler calls handleNext(), which advances the current position in the result set. If we are already at the last position, it wraps to the first result.

const handleNext = () => {
  if (results.length === 0) {
    return;
  }

  if (currentPosition >= results.length - 1) {
    setPosition(0);
    return;
  }

  setPosition(currentPosition + 1);
};

The implementation of the “Previous” button is very similar:

<button title="Previous" onClick={onPrevious} style={displayStyle} className="next-previous">
  <svg width={18} height={18}><use href="icons/daypilot.svg#minichevron-up-2" /></svg>
</button>

In handlePrevious(), we move in the opposite direction and wrap to the last result when we hit the start.

const handlePrevious = () => {
  if (results.length === 0) {
    return;
  }

  if (currentPosition === 0) {
    setPosition(results.length - 1);
    return;
  }

  setPosition(currentPosition - 1);
};

History

  • April 18, 2026: Updated the sample to the current React 19 + Vite builder template, refreshed the DayPilot Pro package to 2026.2.6907, and revised the tutorial text to match the new function-component implementation.