LoginSignup
9
9

More than 1 year has passed since last update.

FullCalendar React+TypeScript Example Projectを関数コンポーネントで書いてみる 01

Last updated at Posted at 2021-07-05

FullCalendarは、さまざまにカスタマイズしたカレンダーがつくれるオープンソースのJavaScriptライブラリです。ReactやVueそしてAngularといったフレームワークにも対応しています。とくに、Reactについては、バーチャルDOMノードを生成するという踏み込んだつくり込みです。

ただ、残念ながらReactを用いた情報があまりありません。まして、TypeScriptまで加えると、ほぼ見当たらない状況です。そうした中で、拠りどころとなるのがFullCalendar公式の「FullCalendar React+TypeScript Example Project」でしょう。本シリーズ記事(全3回)は、このコードをもとにして、チュートリアルのかたちで解説します。

ただし、公式のコードはクラスを用いています。これは、今主流になりつつある関数コンポーネントに書き替えました。ほかにも、モジュール分けしたり、知っておくと便利な機能を加えたり、解説も工夫したつもりです。

サンプル001■React + TypeScript + FullCalendar: Example calendar 01

2107001_001.png
>> CodeSandboxへ

なお、FullCalendarにまだ触れたことがない方は、先に「React + TypeScript + FullCalendar: 簡単なカレンダーをつくる」をお読みいただくとよいでしょう。

カレンダーを表示する

Reactアプリケーションのひな型は、Create React Appを使ってつくります(「React + TypeScriptのひな形作成とFullCalendarのインストール」)。

npx create-react-app react-typescript-fullcalendar --template typescript

そして、プロジェクトのディレクトリ(react-typescript-fullcalendar)に切り替えたら、FullCalendarに加えて、プラグインを3つインストールしてください。

npm install --save @fullcalendar/react @fullcalendar/daygrid @fullcalendar/core @fullcalendar/interaction

まずは、ユーティリティのモジュール(src/event-utils.ts)に、予定のデータ(INITIAL_EVENTS)を配列で定めます(コード001)。予定(イベントオブジェクト)に加えるプロパティはつぎの表001の3つです(「Event Object」参照)。idには一意の数字が与えられるよう、関数(createEventId())をexportしました。日付はYYYY-MM-DD形式で、時刻も添える場合はTで結んでください(「日付と時刻の組合せ」参照)。

表001■イベントオブジェクトのプロパティ1

プロパティ 説明
id イベントを示す一意の識別子となる文字列。getEventByIdに用いることができる。
title イベントとしてカレンダーに示されるタイトルの文字列。
start イベントが始まる日時を示す文字列か数値またはDateオブジェクト。

コード001■ユーティリティモジュール

src/event-utils.ts
import { EventInput } from "@fullcalendar/react";

let eventGuid = 0;
const todayStr = new Date().toISOString().replace(/T.*$/, "");  // 今日の日付をYYYY-MM-DD形式にする
export const createEventId = () => String(eventGuid++);
export const INITIAL_EVENTS: EventInput[] = [
  {
    id: createEventId(),
    title: "All-day event",
    start: todayStr,
  },
  {
    id: createEventId(),
    title: "Timed event",
    start: todayStr + "T12:00:00",  // 時刻はTで結ぶ
  },
];

つぎに、ルートモジュールsrc/App.tsxです。ユーティリティモジュールからimportした予定データ(INITIAL_EVENTS)は、FullCalendarコンポーネントのinitialEventsに与えます。また、3つ読み込んだプラグインのうち、allLocaleslocalesに定めましょう(「日本語表示にする ー localeの設定」参照)。あとのふたつは、pluginsに配列で加えてください。initialViewは、標準的な月単位の"dayGridMonth"です。

src/App.tsx
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import allLocales from '@fullcalendar/core/locales-all';
import interactionPlugin from "@fullcalendar/interaction";
import { INITIAL_EVENTS } from "./event-utils";

function App() {
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar
          plugins={[dayGridPlugin, interactionPlugin]}
          initialView="dayGridMonth"
          initialEvents={INITIAL_EVENTS}
          locales={allLocales}
          locale="ja"
        />
      </div>
    </div>
  );
}

export default App;

CSSは公式プロジェクトのsrc/main.cssの定めに少しだけ手を加えました。冒頭に掲げたCodeSandboxサンプル001のsrc/styles.cssをご覧ください。

イベントオブジェクトを取得する

eventsSetイベントオブジェクトを取り出すためのハンドラです。予定のデータが、初期化されたり、変更になったときに呼び出されます。今は、データが変えられませんから、呼び出されるのは初期化時のみです。そこで、データをコンソールに出力して確かめることにしました。なお、コールバック(handleEvents())はuseCallbackフックで包むのが、Reactのお約束です(「Create React App 入門 09: useCallbackフックで無駄な処理を省く」参照)。

src/App.tsx
import { useCallback, useState } from "react";
// import FullCalendar from "@fullcalendar/react";
import FullCalendar, { EventApi } from "@fullcalendar/react";

function App() {
  const [currentEvents, setCurrentEvents] = useState<EventApi[]>([]);
  const handleEvents = useCallback((events: EventApi[]) => {
    console.log("events:", events);  // 確認用
    setCurrentEvents(events);
  }, []);
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar

          eventsSet={handleEvents}
        />
      </div>
    </div>
  );
}

予定を入力できるようにする

それでは、カレンダーに予定を加えられるようにしましょう。そのためには、FullCalendarコンポーネントのselectabletrueにして日付が選べるようにしておかなければなりません。そのうえで、選んだときのイベントハンドラはselectです。

window.prompt()メソッドはダイアログを開き、ユーザーの入力した文字列が返されます。前後のスペースはString.prototype.trim()で除きました。コールバック(handleDateSelect())が受け取る引数(selectInfo)のviewプロパティから取り出すcalendarが、このカレンダーを操作するオブジェクト(calendarApi)です。

カレンダーオブジェクト(calendarApi)に対してaddEvent()メソッドを呼び出すと、引数のイベントオブジェクトが予定データの配列に加えられます。前掲コード001の予定データの初期値(INITIAL_EVENTS)より、ふたつプロパティが増えました。説明は以下の表002のとおりです。

src/App.tsx
// import FullCalendar, { EventApi } from "@fullcalendar/react";
import FullCalendar, { DateSelectArg, EventApi } from "@fullcalendar/react";

// import { INITIAL_EVENTS } from "./event-utils";
import { INITIAL_EVENTS, createEventId } from './event-utils'

function App() {

  const handleDateSelect = useCallback((selectInfo: DateSelectArg) => {
    let title = prompt("イベントのタイトルを入力してください")?.trim();
    let calendarApi = selectInfo.view.calendar;
    calendarApi.unselect();
    if (title) {
      calendarApi.addEvent({
        id: createEventId(),
        title,
        start: selectInfo.startStr,
        end: selectInfo.endStr,
        allDay: selectInfo.allDay,
      });
    }
  }, []);
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar

          selectable={true}

          select={handleDateSelect}
        />
      </div>
    </div>
  );
}

表002■イベントオブジェクトのプロパティ2

プロパティ 説明
end イベントが終わる日時を示す文字列か数値またはDateオブジェクト。
allDay 終日のイベントとするかどうかの真偽値。trueの場合、時間は示されない。

addEvent()メソッドを呼び出す前に、処理がふたつありました。unselect()は、日付が選択されていない状態に戻します。そして、textを条件判定したのは、ユーザーがタイトルを書き込んでいないか、スペースしか入力していない場合に、予定の追加を取りやめるためです。

予定をクリックで削除する

予定が加えられるようになったので、つぎは削除です。予定の欄をクリックしたら、ダイアログで確認して消してしまえるようにしましょう。追加した予定が書き替えられるようにするには、FullCalendarコンポーネントのeditableプロパティにtrueを与えなければなりません。そして、クリックしたときのイベントハンドラがeventClickです。

コールバック(handleEventClick())が受け取る引数のオブジェクトからは、eventプロパティでそのイベントオブジェクトが受け取れます。削除してよいか確認のダイアログを開くのがwindow.confirm()メソッドです。[OK]ボタンを押せばtrueが返りますので、イベントオブジェクトに対してremove()を呼び出すことにより削除できます。

src/App.tsx
// import FullCalendar, { DateSelectArg, EventApi } from "@fullcalendar/react";
import FullCalendar, {
  DateSelectArg,
  EventApi,
  EventClickArg,
} from "@fullcalendar/react";

function App() {

  const handleEventClick = useCallback((clickInfo: EventClickArg) => {
    if (
      window.confirm(`このイベント「${clickInfo.event.title}」を削除しますか`)
    ) {
      clickInfo.event.remove();
    }
  }, []);
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar

          editable={true}

          eventClick={handleEventClick}
        />
      </div>
    </div>
  );
}

これでカレンダーに予定を書き込んだり、消したりできるようになりました。ルートモジュールsrc/App.tsxの記述全体は、つぎのコード002のとおりです。具体的な動きについては、CodeSandboxに公開した冒頭のサンプル001でお確かめください。

コード002■ルートモジュール

src/App.tsx
import { useCallback, useState } from "react";
import FullCalendar, {
  DateSelectArg,
  EventApi,
  EventClickArg,
} from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import allLocales from '@fullcalendar/core/locales-all';
import interactionPlugin from "@fullcalendar/interaction";
import { INITIAL_EVENTS, createEventId } from './event-utils'

function App() {
  const [currentEvents, setCurrentEvents] = useState<EventApi[]>([]);
  const handleEvents = useCallback((events: EventApi[]) =>
    setCurrentEvents(events)
  , []);
  const handleDateSelect = useCallback((selectInfo: DateSelectArg) => {
    let title = prompt("イベントのタイトルを入力してください")?.trim();
    let calendarApi = selectInfo.view.calendar;
    calendarApi.unselect();
    if (title) {
      calendarApi.addEvent({
        id: createEventId(),
        title,
        start: selectInfo.startStr,
        end: selectInfo.endStr,
        allDay: selectInfo.allDay,
      });
    }
  }, []);
  const handleEventClick = useCallback((clickInfo: EventClickArg) => {
    if (
      window.confirm(`このイベント「${clickInfo.event.title}」を削除しますか`)
    ) {
      clickInfo.event.remove();
    }
  }, []);
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar
          plugins={[dayGridPlugin, interactionPlugin]}
          initialView="dayGridMonth"
          selectable={true}
          editable={true}
          initialEvents={INITIAL_EVENTS}
          locales={allLocales}
          locale="ja"
          eventsSet={handleEvents}
          select={handleDateSelect}
          eventClick={handleEventClick}
        />
      </div>
    </div>
  );
}

export default App;

シリーズ

FullCalendar React+TypeScript Example Projectを関数コンポーネントで書いてみる 02
FullCalendar React+TypeScript Example Projectを関数コンポーネントで書いてみる 03

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9