LoginSignup
75

More than 3 years have passed since last update.

Reactでカレンダーを使う

Posted at

「react datepicker」でググると、「react-datepicker」のライブラリが出てくると思います。
react-datepickerを使う機会があったので、備忘録にreact-datepickerの機能についてご紹介しようと思います。

0. 事前準備

(create-react-appでサンプルを作成しております。)
早速ライブラリをインストールしましょう!

npm install react-datepicker --save

Datepickerを使うコンポーネントにimportさせます。

import DatePicker from "react-datepicker"
import "react-datepicker/dist/react-datepicker.css"

シンプルなDatepickerの書き方はこうです。

const SimpleDatePicker = () => {
  const initialDate = new Date()
  const [startDate, setStartDate] = useState(initialDate)
  const handleChange = (date) => {
    setStartDate(date)
  }

  return (
    <DatePicker
      selected={startDate}
      onChange={handleChange}
    />
  )
}

output2.gif

以下の例では、日付の処理をする関数を作って呼び出すようにしています。

/**
 * JST基準に変換して返す
 * @param {string} dateTimeStr YYYY-MM-DDTHH:mm:00Z
 * @returns {moment.Moment}
 */
const parseAsMoment = (dateTimeStr) => {
  return moment.utc(dateTimeStr, 'YYYY-MM-DDTHH:mm:00Z', 'ja').utcOffset(9)
}

/**
 * 日付形式に変換して返す
 * @param {moment.Moment} momentInstance
 * @returns {string}
 */
const toUtcIso8601str = (momentInstance) => {
  return momentInstance
    .clone()
    .utc()
    .format('YYYY-MM-DDTHH:mm:00Z')
}

1. ある要素をクリックしたらカレンダーを表示させたい場合

Custom inputにDatePickerを表示させるトリガー要素を指定できます。
下の例ではbuttonにクリックされた日付を表示するようにしています。ボタンにはクリックイベントなど特に指定いりません!

const CustomInputDatePicker = () => {
  const [startDate, setStartDate] = useState(toUtcIso8601str(moment()))
  const handleChange = (selectedDate) => {
    setStartDate(toUtcIso8601str(moment(selectedDate)))
  }

  return (
    <DatePicker
      selected={moment(startDate).toDate()}
      onChange={handleChange}
      customInput={
        <button>
          {parseAsMoment(startDate).format('YYYY/MM/DD')}
        </button>
      }
    />
  )
}

output3.gif

2. カレンダーのヘッダーを変更したい場合

Custom headerを使うと、カレンダー上の年月を変更できるセレクトボックスと、次月前月への矢印ボタンを自分でカスタマイズすることができます。

... // 省略
import getMonth from 'date-fns/getMonth'
import getYear from 'date-fns/getYear'
import _ from 'lodash'
...

const years = _.range(2000, getYear(new Date()) + 1, 1)
const months = Array.from(Array(12).keys())

...

const CustomHeaderDatePicker = () => {
  const [startDate, setStartDate] = useState(toUtcIso8601str(moment()))
  const handleChange = (selectedDate) => {
    setStartDate(toUtcIso8601str(moment(selectedDate)))
  }

  return (
    <DatePicker
      selected={moment(startDate).toDate()}
      onChange={handleChange}
      renderCustomHeader={({
        date,
        changeYear,
        changeMonth,
        decreaseMonth,
        increaseMonth,
        prevMonthButtonDisabled,
        nextMonthButtonDisabled
      }) => (
        <div>
          <button
            onClick={decreaseMonth}
            disabled={prevMonthButtonDisabled}
          >
            前月へ移動
          </button>
          <select
            value={getYear(date)}
            onChange={({ target: { value } }) => changeYear(value)}
          >
            {years.map((option) => (
              <option key={option} value={option}>
                {option}
              </option>
            ))}
          </select>
          <select
            value={getMonth(date)}
            onChange={({ target: { value } }) => changeMonth(value)}
          >
            {months.map((option) => (
              <option key={option} value={option}>
                {option + 1}
              </option>
            ))}
          </select>
          <button
            onClick={increaseMonth}
            disabled={nextMonthButtonDisabled}
          >
            次月へ移動
          </button>
        </div>
      )}
    />
  )
}

output4.gif

3. カレンダーをラップする時の小ネタ

Calerndar container内に、カレンダーをラップする要素を指定できます。
見出しやテキスト、他のイベントを設置したい時に使えそうです。
下の例ではcalendarContainerにラップする<DatetimePickerWrapper />コンポーネントを指定しています。childrenにカレンダーが入ります。

const ContainerDatePicker = () => {
  const [startDate, setStartDate] = useState(toUtcIso8601str(moment()))
  const handleChange = (selectedDate) => {
    setStartDate(toUtcIso8601str(moment(selectedDate)))
  }

  return (
    <DatePicker
      selected={moment(startDate).toDate()}
      onChange={handleChange}
      calendarContainer={DatetimePickerWrapper}
    />
  )
}

const DatetimePickerWrapper = ({ children }) => {
  return (
    <div style={{
      overflow: 'hidden',
      padding: 30,
      backgroundColor: 'skyblue',
      boxShadow: '6px 6px #668AD8',
    }}>
      {children}
    </div>
  )
}

output5.gif

4. カレンダーの範囲を指定する

公式サイトのDemoにあるDate Rangeを使うと、日付の範囲を指定できます。
DatePickerを2つ置いて、開始日時を選択する方はselectsStart、終了日時を選択する方はselectsEndを指定します。
また、それぞれ日付を選択するchangeイベントを用意し、選択された日にちのselectedのvalueも開始・終了のステートを指定します。

下のサンプルでは開始日を今日より1週間前、終了日を今日にしています。

const DateRangeDatePicker = () => {
  const [startDate, setStartDate] = useState(toUtcIso8601str(moment().subtract(7, 'days')))
  const [endDate, setEndDate] = useState(toUtcIso8601str(moment()))
  const handleChangeStart = (selectedDate) => {
    setStartDate(toUtcIso8601str(moment(selectedDate)))
  }
  const handleChangeEnd = (selectedDate) => {
    setEndDate(toUtcIso8601str(moment(selectedDate)))
  }

  return (
    <Fragment>
      <DatePicker
        selected={moment(startDate).toDate()}
        selectsStart
        startDate={moment(startDate).toDate()}
        endDate={moment(endDate).toDate()}
        onChange={handleChangeStart}
      />
      <DatePicker
        selected={moment(endDate).toDate()}
        selectsEnd
        startDate={moment(startDate).toDate()}
        endDate={moment(endDate).toDate()}
        onChange={handleChangeEnd}
      />
    </Fragment>
  )
}

Inlineを指定するとカレンダーがベタ表示されるようになるので、使い勝手がよくなりそうです!

output6.gif

5. カレンダーを日本語対応させる

Local"ja"を指定すると、日本語になります。

... // 省略
import DatePicker, { registerLocale } from 'react-datepicker'
import ja from 'date-fns/locale/ja'
registerLocale('ja', ja)
...
const LocalDatePicker = () => {
  const [startDate, setStartDate] = useState(toUtcIso8601str(moment()))
  const handleChange = (selectedDate) => {
    setStartDate(toUtcIso8601str(moment(selectedDate)))
  }

  return (
    <Fragment>
      <DatePicker
        locale="ja"
        selected={moment(startDate).toDate()}
        onChange={handleChange}
      />
    </Fragment>
  )
}

output7.gif

6. カレンダーの複数表示

カレンダーを2ヶ月続けて横並び表示させる場合はMultiple monthsを使います。

下の例では「今月」「先月」をクリックした時、開始・終了日をsetStateしてハイライトさせています。
※注意:下の例はChangeイベント系を除いて書いています!monthsShowninline等の使い方だけご参考ください。

const MultipleDatePicker = () => {
  const [startDate, setStartDate] = useState(toUtcIso8601str(moment().subtract(7, 'days')))
  const [endDate, setEndDate] = useState(toUtcIso8601str(moment()))

  ... // 省略

  return (
    <div>
      <p>開始日: {parseAsMoment(startDate).format('YYYY/MM/DD')}</p>
      <p>終了日: {parseAsMoment(endDate).format('YYYY/MM/DD')}</p>
      <DatePicker
        inline
        selected={moment(startDate).toDate()}
        startDate={moment(startDate).toDate()}
        endDate={moment(endDate).toDate()}
        onChange={handleChange}
        monthsShown={2}
        calendarContainer={({ children }) => (
          <div>
            <div style={{ marginBottom: 20 }}>
              <button onClick={() => handleChangeDateRange('thisMonth')}>今月</button>
              <button onClick={() => handleChangeDateRange('lastMonth')}>先月</button>
            </div>
            {children}
          </div>
        )}
      />
    </div>
  )
}

output8.gif

終わりに

「こんなオプションも用意してくれてるのか!」と感動したので、備忘録として残しました笑。
上記で紹介した指定をした上でスタイルを整えてあげると、下のような感じで良い感じのカレンダーが出来上がります!

output.gif

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
75