ReactのライブラリMaterial-UIを使って入力フォームを作成する方法

目次

1.はじめに
2.React, Material-UIとは
3.作成物
4.作り方
 ①Materual UI のインストール
 ②TextFieldを配置
 ③入力内容の反映
 ④エラー表示
 ⑤type=”date”
 ⑥type=”number”
 ⑦AutoComplete
 ⑧CheckBox
 ⑨useStateの整理
5.おわりに

1.はじめに

こんにちは!システム開発部新入社員の菊池です。

多くのWebアプリケーションでは、ユーザーが入力した情報に応じて処理を実行する機能が必要となります。今回はReactのコンポーネントライブラリである『Material-UI』を使った入力フォームの作成方法についてご説明します。

(※Material-UI以外の環境構築については省略します。)

2.React, Material-UIとは

Reactとは、WebサイトのUI開発に特化したJavaScriptのライブラリの一種で、SPA(Single Page Application)の開発に広く利用されています。またReactの特徴として、仮想DOMを用いた描画速度の高速化、コンポーネントベースの開発によるUIパーツの再利用化、などが挙げられます。

Material-UIとはReact用のコンポーネントライブラリです。Material-UIを利用することで、手軽に見栄えの良いUIを作成することができます。
Material UI: React components that implement Material Design (mui.com)

3.作成物

今回作成する入力フォームの完成品はこちらです。上部のフォームに入力された内容を下部に表示します。

4.作り方

①Materual UI のインストール

以下のコマンドを実行します

npm install @mui/material @emotion/react @emotion/styled

②TextFieldを配置

今回はMaterial-UIのTextFieldコンポーネントを使用します。

https://mui.com/material-ui/react-text-field/

Material-UIから使いたいコンポーネントをインポートし、タグを配置します。

import React from "react"; 
import "./App.css"; 

import { TextField } from "@mui/material";

const TextFieldStyle = {
  width: "200px",
};

const App = () => {
  return (
    <div className="App">
      <TextField 
        label="Input" 
        style={TextFieldStyle}
      /> 
    </div>
  );
};

export default App;

プロパティ

  • label:TextFieldに名前を付けることができます。
  • style:CSSのスタイルを適用できます。

Material-UIのコンポーネントはstyle以外にも見た目を変化させるプロパティが豊富にあります。詳細は公式ドキュメントを参照してください。

https://mui.com/material-ui/api/text-field/

③入力内容の反映

TextFieldに入力した内容を画面に反映させます。

const [input, setInput] = useState(""); //入力内容を保持
const onChangeInput = (event) => {      //入力内容が変化したときに実行する関数
    setInput(event.target.value); 
  };
<TextField 
  label="input" 
  style={TextFieldStyle}
  onChange={onChangeInput} 
/>
<p>{input}</p>

プロパティ

  • onChange:入力内容(value)が変化したときに実行する関数(onChangeInput)を設定できます。関数の引数にeventを設定できます。入力内容はevent.target.valueに格納されています。

入力内容の取得はonChangeプロパティとReactのuseStateで実装します。

useStateとは状態を扱う関数です。左側のinputは状態の値、setInputは値を更新するための関数です。それぞれの名前は自由に設定できます。useState()の中にinputの初期値を設定できますが、今回は空欄(””)になっています。

onChangeで入力内容を取得し、その値をinputに代入、最後に<p>タグでinputを表示することで入力内容を反映しています。

④エラー表示

TextFieldが空欄の時にエラーを表示します。必要な機能は以下の通りです。

  • TextFieldから離れたとき、入力内容が空欄ならばエラーを表示する
  • TextFieldに文字が入力されたらエラーを非表示にする
  • ページを開いた際、TextFieldは空欄だがエラーは非表示にする
const [inputError, setInputError] = useState(false); //エラーの状態を保持

const onChangeInput = (event) => {
  setInput(event.target.value);
  if (event.target.value) {
    setInputError(false);
  }
};
const onBlurInput = (event) => {     //TextFieldからフォーカスが離れたときに実行する関数
  if (!event.target.value) {
    setInputError(true);
  }
};
<TextField
  label="Input"
  style={TextFieldStyle}
  onChange={onChangeInput}
  onBlur={onBlurInput}
  error={inputError}
  helperText={inputError ? "入力してください" : ""}
/>

プロパティ

  • onBlur:オブジェクトからフォーカスが外れたときに実行する関数を設定できます。
  • error:値がtrueの時にtextFieldを赤く表示します。
  • helperText:TextFieldの下に表示するテキストを設定できます。errorプロパティがtrueの時はhelperTextも赤く表示されます。

エラー表示はonChange, onBlur, error, helperTextプロパティ、useStateで実装します。

まずはエラー用のuseStateを設定します。値はboolean型です。

const [inputError, setInputError] = useState(false); //エラーの状態を保持

次にonChange, onBlurプロパティです。これらの処理でinputErrorを変化させます。

const onChangeInput = (event) => {
    setInput(event.target.value);
    if (event.target.value) {      //入力されたらエラーを解除する
      setInputError(false);
    }
  };
  const onBlurInput = (event) => {     //TextFieldからフォーカスが離れたときに実行する関数
    if (!event.target.value) {     //空欄ならエラーをtrueにする
      setInputError(true);
    }
  };

最後にerror, helperTextプロパティです。

どちらのプロパティもinputErrorの値を参照します。helpertextはtrueの時だけテキストを表示するようにします。

<TextField
  label="Input"
  style={TextFieldStyle}
  onChange={onChangeInput}
  onBlur={onBlurInput}
  error={inputError}
  helperText={inputError ? "入力してください" : ""}
/>

以上で入力フォームの基本的な機能を実装することができました。

⑤type=”date”

TextFieldにtypeプロパティを設定することで、TextFieldに入力できるデータの型を指定できます。

type=”date”とすることで、日付用の入力フォームを作成できます。

import dayjs from "dayjs";
const today = dayjs().format("YYYY-MM-DD"); //今日の日付を取得

const [date, setDate] = useState(today);                      //日付を保持
const [dateBlankError, setDateBlankError] = useState(false);  //日付が空欄の時のエラー
const [dateTodayError, setDateTodayError] = useState(false);  //今日より前の日付が入力された時のエラー

// プロパティ
const onChangeDate = (event) => {
  setDate(event.target.value);
  if (event.target.value) {     //入力されたらエラーを解除する
    setDateBlankError(false);
  }
  if (!dayjs(event.target.value).isBefore(today)) {     //入力された日付が今日の日付と同じか後なら、エラーを解除する
    setDateTodayError(false);
  }
};
const onBlurDate = (event) => {
  if (!event.target.value) {     //空欄ならエラーをtrueにする
    setDateBlankError(true);
  }
  if (dayjs(event.target.value).isBefore(today)) {     //入力された日付が今日の日付よりも前なら、エラーをtrueにする
    setDateTodayError(true);
  }
};
<TextField
  type="date"
  label="Date"
  style={TextFieldStyle}
  value={date}
  onChange={onChangeDate}
  onBlur={onBlurDate}
  error={dateBlankError || dateTodayError}
  helperText={
    dateBlankError
      ? "日付を入力してください"
      : dateTodayError
      ? "今日より前の日付は入力できません"
      : ""
  }
/>

プロパティ

  • type:TextFieldに入力できるデータの型を指定
  • value:TextFieldに表示する文字(入力内容)を制御

JavaScriptで日付操作を可能にするライブラリである『dayjs』をインストールします。

以下のコマンドを実行します

npm install dayjs

今日の日付を取得します

const today = dayjs().format("YYYY-MM-DD"); //今日の日付を取得

通常のTextFieldと比べて、入力内容の反映やエラー処理の基本的な仕組みは変わりません。

Date用のuseStateを作成します。今回は2種類のエラーを実装するので、エラー用のuseStateは2つ用意します。

const [date, setDate] = useState(today);                      //日付を保持。初期値は今日の日付
const [dateBlankError, setDateBlankError] = useState(false);  //日付が空欄の時のエラー
const [dateTodayError, setDateTodayError] = useState(false);  //今日より前の日付が入力されたときのエラー

表示内容はvalueプロパティで制御します。useStateで、valueの初期値を今日の日付に設定しています。

value(入力内容)が空欄の場合、以下のようにTextFieldのラベルと日付のフォーマットが重なって表示されてしまいます。これを避けるためにvalueの初期値を設定しています。

⑥type=”number”

type=”number”とすることで数字用の入力フォームを作成できます。

今回は1~999の整数を入力できるフォームを作成します。

※2024年10月2日現在、公式ドキュメントではTextFieldのtype=”number”の代わりに、Base-UIのNumber Inputを使用することが推奨されています。

https://mui.com/material-ui/react-text-field/#type-quot-number-quot

https://mui.com/base-ui/react-number-input/

const [number, setNumber] = useState();
const [numberBlankError, setNumberBlankError] = useState(false);
const [numberFormatError, setNumberFormatError] = useState(false);

//Numberのプロパティ
const onChangeNumber = (event) => {
  setNumber(event.target.value.slice(0, 3));     //入力内容から3桁目(3文字目)までを取得する
  if (event.target.value) {     //入力されたらエラーを解除する
    setNumberBlankError(false);
  }
  if (
    !(
      event.target.value < 1 ||            //入力内容が適切な場合、エラーを解除する
      event.target.value > 999 ||
      !Number.isInteger(Number(event.target.value))
    )
  ) {
    setNumberFormatError(false);
  }
};
const onBlurNumber = (event) => {
  if (!event.target.value) {     //空欄ならエラーをtrueにする
    setNumberBlankError(true);
  }
  if (
    event.target.value < 1 ||             //入力内容が不適切な場合、エラーをtrueにする
    event.target.value > 999 ||
    !Number.isInteger(Number(event.target.value))
  ) {
    setNumberFormatError(true);
  }
};
<TextField
  type="number"
  label="Number"
  style={TextFieldStyle}
  value={number}
  onChange={onChangeNumber}
  onBlur={onBlurNumber}
  error={numberBlankError || numberFormatError}
  helperText={
    numberBlankError
      ? "数値を入力してください"
      : numberFormatError
      ? "入力内容が適切ではありません"
      : ""
  }
  inputProps={{     //入力内容を制限(カウントボタンのみ)
    min: 1,
    max: 999,
  }}
/>

プロパティ

  • inputProps:入力内容を制限できます。

今回は以下のエラーを実装します。

  • 入力内容が空欄の時はエラーを表示
  • 入力内容が適切ではないならエラーを表示
    ・入力内容が小数である
    ・入力内容が1~999の範囲にない

inputPropsプロパティは入力内容を制限することができます。しかし、これはTextField右側にあるカウントボタンにしか適用されないため、キーボードからの手入力では指定した範囲外の数値を入力することが可能です。したがって、手入力による入力内容を制限するコードを追加する必要があります。

onChangeのsetNumber()で、event.target.valueの3文字目(3桁目)までをスライスするように設定することで、4桁以上の数値の入力を制限することができます。

const onChangeNumber = (event) => {
  setNumber(event.target.value.slice(0, 3));

type=”number”では数字以外にの一部の文字(‘e’, ‘+’, ‘-‘, ‘.’)が入力可能となっているため、それらを制御する必要があります。特にeを使った指数表示は、入力が3文字以内でも4桁以上の数を入力することができます。

if (
  !(
    event.target.value < 1 ||
    event.target.value > 999 ||
    !Number.isInteger(Number(event.target.value))
  )
)

⑦AutoComplete

TextFieldをAutoCompleteコンポーネントと組み合わせることで、検索に対応したセレクトボックスを実装することができます。

https://mui.com/material-ui/react-autocomplete/

import { TextField, Autocomplete } from "@mui/material";

const areaList = [  //セレクトボックスの選択肢
  "アジア",
  "ヨーロッパ",
  "アフリカ",
  "北アメリカ",
  "南アメリカ",
  "オセアニア",
];

const [area, setArea] = useState();
const [areaError, setAreaError] = useState(false);

// Areaのプロパティ
const onChangeArea = (event, value) => {
  setArea(value);
  if (value) {     //入力されたらエラーを解除
    setAreaError(false);
  }
};
const onBlurArea = (event) => {
  if (!event.target.value) {     //空欄ならエラーをtrueにする
    setAreaError(true);
  }
};
<Autocomplete
  options={areaList}     //選択肢を指定
  onChange={onChangeArea}
  renderInput={(params) => (
    <TextField
      {...params}
      label="Area"
      style={TextFieldStyle}
      onBlur={onBlurArea}
      error={areaError}
      helperText={areaError ? "地域を入力してください" : ""}
    />
  )}
/>

プロパティ

  • options:セレクトボックスの選択肢としてリストを設定できます。
  • renderInput:入力用のコンポーネントを指定します。今回はTextFieldを指定します。

AutoCompleteはonBLurプロパティを持たないため、TextFieldにonBlurを設定します。

onChangeのプロパティもTextFiledに設定することができます。しかしセレクトボックスはAutoCompleteの機能であるため、セレクトボックスから選択して入力された内容(value)をTextFieldから取得することはできません。したがって入力内容を取得するために、onChangeはAutoCompleteコンポーネントに設定する方が必要があります。

ちなみにTextFileldのonChangeプロパティは、キーボードから手入力されたvalueのみ取得することができます。

⑧CheckBox

Checkboxコンポーネントを用いることでチェックボックスを実装できます。チェックボックスにラベルを追加する場合はFormControlLabelコンポーネントと組み合わせます。

https://mui.com/material-ui/react-checkbox/

https://mui.com/material-ui/api/form-control-label/

import {
  TextField,
  Autocomplete,
  FormControlLabel,
  Checkbox,
} from "@mui/material";

const [check, setCheck] = useState(false); //チェックボックスの状態を保持

// CheckBoxのプロパティ
const onChangeCheck = () => {
  setCheck(!check);       //trueとfalseを反転
};
<FormControlLabel
  control={<Checkbox onChange={onChangeCheck} checked={check} />}
  style={{
    width: "300px",
    margin: "auto",
    fontSize: "10px",
  }}
  label="Check"
/>

プロパティ

FormControlLabel

  control:表示させるオブジェクトを指定

Checkbox

  check:チェックボックスの表示を管理

⑨useStateの整理

複数の入力フォームを実装すると、入力内容やエラーを管理しているuseStateの数が増えてきます。

これらを1つのuseStateに整理して可読性を向上させましょう。

useStateの値を連想配列にすることで、1つのuseStateで複数の値を管理することができます。

const defaultData = { //dataの初期値
  input: "",
  date: today,
  number: "",
  area: "",
  check: false,
};
const defaultError = { //errorの初期値
  input: false,
  dateBlank: false,
  dateToday: false,
  numberBlank: false,
  numberFormat: false,
  area: false,
};
const [data, setData] = useState(defaultData);
const [error, setError] = useState(defaultError);

useStateの値を更新する関数changeData, changeErrorを定義し、引数で連想配列のキー(name)とバリュー(value)を与えます。

またuseStateのset~関数では変更前の値をprevDataで取得することができます。prevDataは連想配列になっているため、分割代入で配列の中身だけを取り出してからnameとvalueで値を更新します。

//Dataの更新
const changeData = (name, value) => {
  setData((prevData) => ({
    ...prevData,
    [name]: value,
  }));
};
//Errorの更新
const changeError = (name, value) => {
  setError((prevData) => ({
    ...prevData,
    [name]: value,
  }));
};

各入力フォームでnameとvalueを動的に取得できるようにします。nameはevent.target.nameで取得できます。

例:input

// Inputのプロパティ
const onChangeInput = (event) => {
  const { name, value } = event.target;     //nameとvalueを取得
  changeData(name, value);
  if (value) {
    changeError(name, false);
  }
};
const onBlurInput = (event) => {
  const { name, value } = event.target;     //nameとvalueを取得
  if (!value) {
    changeError(name, true);
  }
};
<TextField
  label="Input"
  name="input"              //連想配列のキーとなるnameを指定
  style={TextFieldStyle}
  onChange={onChangeInput}
  onBlur={onBlurInput}
  error={error.input}
  helperText={error.input ? "入力してください" : ""}
/>

プロパティ

  • name:オブジェクトに識別や検索に使用できる文字列を設定

AutoCompleteとCheckboxはnameをevent.targetから取得することができません。

AutoCompleteのnameは、useRefのgetAttributeで直接取得することができます。

const ref0 = useRef(); //AutoCompleteのnameを動的に取得

// Areaのプロパティ
const onChangeArea = (event, value) => {
  console.log(event);
  changeData(ref0.current.getAttribute("name"), value);
  if (value) {
    changeError(ref0.current.getAttribute("name"), false);
  }
};
<Autocomplete
  name="area"
  ref={ref0}              //useRefを使用
  options={areaList}
  onChange={onChangeArea}
  renderInput={(params) => (
    <TextField
      {...params}
      label="Area"
      name="area"
      style={TextFieldStyle}
      onBlur={onBlurArea}
      error={error.area}
      helperText={error.area ? "地域を入力してください" : ""}
    />
  )}
/>

Checkboxのnameはevent.nativeEvent.srcElement.nameから取得することができます。

// CheckBoxのプロパティ
const onChangeCheck = (event) => {
  changeData(event.nativeEvent.srcElement.name, !data.check);
};

5.おわりに

Material-UIには今回ご紹介したコンポーネントやプロパティ以外にも便利な機能が豊富にあります。

今後もこのようなコンポーネントを活用して、お客様にも便利に感じていただけるアプリケーションを作り続けていきます。