Header image

クラッソーネの開発者がエンジニアリングに関することもそうでないことも綴っています!

MUI(Material UI v5) の Date Picker で、任意の日付以前を選択不可にする

MUI(Material UI v5) の Date Picker で、任意の日付以前を選択不可にする

こんにちは、プロダクト開発部の服部です。

初夏を思わせるような陽気が続いていますが、まだまだスギ花粉は飛んでいるようです。

今回は、MUI(Material UI v5)の Date Picker を使った日付選択範囲指定の小技を紹介します。

やりたいこと

実際に業務で「不規則に発生する契約日について、契約日以前の日付へ変更不可の Date Picker カレンダーを実装」というタスクが発生しました。

受け取った日付以前が選択不可の Date Picker カレンダーを実装したいと思います。

(この記事では、Date Picker API のうち、StaticDatePicker APIを利用しています。)

本日が 4 月 18 日で、契約日を直前の金曜日 4 月 15 日であったと仮定すると、できあがったカレンダーのイメージは次の通りです。

何もしない 契約日を含めない 契約日を含める
何もしない 4月15日を含めない 4月15日を含める

契約日を含めない場合(契約日前日までの日付が選択不可)

Date Picker は日付ライブラリに依存しているので、今回は「date-fns」ライブラリを利用しました。
LocalizationProvider コンポーネントでラップして、AdapterDateFns を設定します。

参考:MUI:Date / Time pickers

まずは date-fns のpreviousFriday()関数を利用して、契約日である「直前の金曜日」を取得します。

previousFriday(new Date());

参考:date-fns:previousFriday

Date Picker の便利なプロパティー minDate に日付を設定するだけで、契約日前日までが選択不可になります。(本日が 4 月 18 日の場合のイメージ)

本日が4月18日の場合:4月15日を含めない

minDate={'契約日'}

// 契約日の前日までを選択不可にする
minDate={previousFriday(new Date())}
import { useState } from "react";

import AdapterDateFns from "@mui/lab/AdapterDateFns";
import LocalizationProvider from "@mui/lab/LocalizationProvider";
import StaticDate Picker from "@mui/lab/StaticDate Picker";
import ja from "date-fns/locale/ja";
import { previousFriday} from "date-fns";
import { TextField, Box } from "@mui/material";

// 契約日のダミーデータ作成
// 直前の金曜日を契約日とする
const referenceDate = previousFriday(new Date());

export const App = () => {
  const [value, setValue] = useState<Date | number | null>(new Date());
  return (
    <LocalizationProvider dateAdapter={AdapterDateFns} locale={ja}>
      <Box p={2}>
        <StaticDate Picker
          displayStaticWrapperAs="desktop"
          value={value}
          onChange={(newValue) => {
            setValue(newValue);
          }}
          renderInput={(params) => <TextField {...params} />}
          // 契約日前日までが選択不可になる
          minDate={referenceDate}
        />
      </Box>
    </LocalizationProvider>
  );
};

以上で契約日前日までの日付が選択不可となりました。

契約日を含める場合(契約日以前の日付が選択不可)

date-fns のaddDays()関数を利用して、「契約日」に n 日プラスします。

addDays("契約日", n);

// 契約日当日までを選択不可にする
minDate={addDays(previousFriday(new Date()), 1)}

参考:date-fns:addDays

(本日が 4 月 18 日の場合のイメージ)
本日が4月18日の場合:4月15日を含める

App.js
import { useState } from "react";

import AdapterDateFns from "@mui/lab/AdapterDateFns";
import LocalizationProvider from "@mui/lab/LocalizationProvider";
import StaticDate Picker from "@mui/lab/StaticDate Picker";
import ja from "date-fns/locale/ja";
import { previousFriday, addDays } from "date-fns";
import { TextField, Box } from "@mui/material";

// 契約日のダミーデータを作成
// 直前の金曜日を契約日とする
const referenceDate = previousFriday(new Date());

// 契約日以前はカレンダーを選択不可にする
// date-fns の addDays を利用して契約日に1日プラス
const theDayAfterReferenceDate = addDays(referenceDate, 1);

export const App = () => {
  const [value, setValue] = useState<Date | number | null>(new Date());
  return (
    <LocalizationProvider dateAdapter={AdapterDateFns} locale={ja}>
      <Box p={2}>
        <StaticDate Picker
          displayStaticWrapperAs="desktop"
          value={value}
          onChange={(newValue) => {
            setValue(newValue);
          }}
          renderInput={(params) => <TextField {...params} />}
          // 契約日前日までと契約日が選択不可になる
          minDate={theDayAfterReferenceDate}
        />
      </Box>
    </LocalizationProvider>
  );
};

以上で契約日以前の日付が選択不可となりました。

簡単な実装ですが、やりたいことを実現する方法に辿り着くのに時間がかかってしまいました。(他にも色々な方法があるはず)
予め Date Picker や date-fns でできそうなことを把握しておくことで、日付関連の実装がかなり楽になりそうです。

コードはCodeSandboxに置いておきます。

参考サイト:

おまけ

React v18.0: March 29, 2022 by The React Team

サンプルコードを書くにあたり、React 17 で書いたコードを React 18 に切り替えたところ、
ReactDOM.renderは React18 ではサポートされなくなりました。代わりにcreateRootを使用してください」という旨の warning が表示されました。

表示されたwarning
Warning: ReactDOM.renderis no longer supported in React 18.
Use createRoot instead. Until you switch to the new API,
your app will behave as if it's running React 17.
Learn more: https://reactjs.org/link/switch-to-createroot

React18 でサポートされなくなったReactDOM.renderの代わりにcreateRootを利用したコードに書き換えてみます。
React18 にアップグレードするための方法に従って修正したコードは以下の通りです。

before:

index.js
import { render } from "react-dom";
import { StyledEngineProvider } from "@mui/material/styles";

import { App } from "./App";

const rootElement = document.getElementById("root");

render(
  <StyledEngineProvider injectFirst>
    <App />
  </StyledEngineProvider>,
  rootElement
);

after:

index.js
import * as ReactDOMClient from "react-dom/client";
import { StyledEngineProvider } from "@mui/material/styles";

import { App } from "./App";

const container = document.getElementById("root");

const root = ReactDOMClient.createRoot(container);

root.render(
  <StyledEngineProvider injectFirst>
    <App />
  </StyledEngineProvider>
);

無事 warning が消えました!

おわりに

クラッソーネでは、プロダクトとチームの双方をより良く改善していけるエンジニアを募集中です!

https://www.crassone.co.jp/recruit/engineer/