React + TypeScript + FullCalendar: 簡単なカレンダーをつくる

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

[^1]: [有償のプラン](https://fullcalendar.io/pricing)も用意されており、追加のプラグインやサポートが提供されます。

とはいえ、まだReactを用いた情報があまりなく、TypeScriptまで含めると、調べるのに苦労するのが現状です。本稿では、ReactとTypeScriptを使って、FullCalendarによるごく簡単なカレンダーのサンプルをつくってみます(図001)。なお、公式サイトのコードサンプルは、多くがクラスを用いています。本稿では、今主流となりつつある関数コンポーネントでコードを書くことにしました。

#### サンプル001■React + TypeScript + FullCalendar: Basic calendar
[![2106002_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/02ff49af-342e-0605-755b-8e18ef983e82.png)
\>> CodeSandboxへ](https://codesandbox.io/s/react-typescript-fullcalendar-basic-calendar-z8gwd)


# React + TypeScriptのひな形作成とFullCalendarのインストール
Reactアプリケーションのひな型は、Create React Appを使うのが簡単です(「Create React App 入門 01: 3×3マスのゲーム盤をつくる」01「[Reactアプリケーションのひな形をつくる](https://www.fumiononaka.com/Business/html5/FN2101001.html#sec01)」)。コマンドのオプションに`--template typescript`を加えれば、プロジェクトにTypeScriptの設定が含まれます(プロジェクト名は`react-typescript-fullcalendar`としました)。

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

つぎに、プロジェクトのディレクトリ(`react-typescript-fullcalendar`)に切り替えて、FullCalendarのインストールです。カレンダーのビューに使うプラグインの`daygrid`も加えてください。

```terminal
npm install --save @fullcalendar/react @fullcalendar/daygrid
```


# カレンダーを表示する
FullCalendarの`import`は、`FullCalendar`を先に、プラグインの`dayGridPlugin`はあとから読み込んでください。`FullCalendar`コンポーネントの`plugins`にプラグインを配列要素として加えます。`initialView`は、標準的な月単位のカレンダー`dayGridMonth`です([Docs / Month View](https://fullcalendar.io/docs/month-view)参照)。

```react:src/App.tsx
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid"; // pluginは、あとから

function App() {
  return (
    <div>
      <FullCalendar plugins={[dayGridPlugin]} initialView="dayGridMonth" />
    </div>
  );
}

export default App;
```

これで、月単位のカレンダーが表示されます(図001)。

#### 図001■月単位のカレンダー
![2106002_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/6942610b-5d02-e7a9-f09e-944ead6fd531.png)


# 日本語表示にする ー localeの設定
`FullCalendar`コンポーネントには[`locale`](https://fullcalendar.io/docs/locale)が定められます。値を`"ja"`とすれば日本語表示です。

```react:src/App.tsx
function App() {
  return (
    <div>
      <FullCalendar

        locale="ja"
      />
    </div>
  );
}
```

ところが、これだけでは完全な日本語表記にはなりません(図002)。

#### 図002■ボタンが日本語表記にならない
![2106002_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/71c60986-be51-da24-d1dd-8390b40a536b.png)

公式ドキュメントの「[locale](https://fullcalendar.io/docs/locale)」を読んでも、Reactの場合について具体的な説明がありませんでした。試行錯誤してみたところ、つぎのプラグインのインストールがいるようです。

```terminal
npm install --save @fullcalendar/core
```

そのうえで、`allLocales`を`import`して、`locales`に定めたところ、無事日本語表記にできました。

```react:src/App.tsx
import allLocales from '@fullcalendar/core/locales-all';

function App() {
  return (
    <div>
      <FullCalendar

        locales={allLocales}
        locale="ja"
      />
    </div>
  );
}
```

# 予定を加える
カレンダーに予定を加えましょう。予定はオブジェクトにして、`FullCalendar`コンポーネントの`events`に配列で与えてください。基本的なプロパティとしては、イベント名の`title`と日時の`date`です。日付のフォーマットは、文字列でYYYY-MM-DDとします。今月の文字列が得られるよう、関数(`thisMonth()`)を加えました。

```react:src/App.tsx
const thisMonth = () => {
  const today = new Date();
  return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(
    2,
    "0"
  )}`;
};
function App() {
  return (
    <div>
      <FullCalendar

        events={[
          { title: "event 1", date: `${thisMonth()}-01` },
          { title: "event 2", date: `${thisMonth()}-02` },
        ]}
      />
    </div>
  );
}
```

これで、ふたつの予定がカレンダーに加わります(図003)。

#### 図003■ふたつの予定が加わった
![2106002_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128949/df08ed64-44f3-5a58-eb66-b8b72b3baffa.png)


# クリックした日付を表示する
簡単なインタラクションを加えましょう。クリックしたら、その日付をダイアログで表示することにします。そのためには、プラグインをインストールしなければなりません。

```terminal
npm install --save @fullcalendar/interaction
```

`import`した`interactionPlugin`は、`FullCalendar`コンポーネントの`plugins`に加えてください。用いるイベントは[`dateClick`](https://fullcalendar.io/docs/dateClick)です。コールバックは、Reactの`useCallback`フックに定めた方がよいでしょう。ハンドラから受け取る引数(`arg`)は、プラグインから読み込んだ`DateClickArg`で型づけします。これで、カレンダーのクリックした日付が、警告ダイアログで示されるはずです。

```react:src/App.tsx
import { useCallback } from "react";

import interactionPlugin, { DateClickArg } from "@fullcalendar/interaction";

function App() {
  const handleDateClick = useCallback((arg: DateClickArg) => {
    alert(arg.dateStr);
  }, []);
  return (
    <div>
      <FullCalendar
        // plugins={[dayGridPlugin]}
        plugins={[dayGridPlugin, interactionPlugin]}

        dateClick={handleDateClick}
      />
    </div>
  );
}
```

書き上がったルートモジュール`src/App.tsx`の記述全体は、つぎのコード001のとおりです。具体的なコードの動きについては、冒頭のサンプル001をご参照ください。

#### コード001■ルートモジュール
```react:src/App.tsx
import { useCallback } from "react";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import allLocales from '@fullcalendar/core/locales-all';
import interactionPlugin, { DateClickArg } from "@fullcalendar/interaction";
const thisMonth = () => {
  const today = new Date();
  return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(
    2,
    "0"
  )}`;
};
function App() {
  const handleDateClick = useCallback((arg: DateClickArg) => {
    alert(arg.dateStr);
  }, []);
  return (
    <div>
      <FullCalendar
        plugins={[dayGridPlugin, interactionPlugin]}
        initialView="dayGridMonth"
        locales={allLocales}
        locale="ja"
        events={[
          { title: "event 1", date: `${thisMonth()}-01` },
          { title: "event 2", date: `${thisMonth()}-02` },
        ]}
        dateClick={handleDateClick}
      />
    </div>
  );
}

export default App;
```