0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【React】コンボボックスを作ってみた

Posted at

はじめに

前回は、簡単な入力フォームをReactにて作成しました。

今回は、プルダウンのコンボボックスを作ってみます。
また、Material-UIのライブラリも併せて使ってみたいと思います。

環境・バージョン情報

  • Visual Studio Code v1.70.1
  • Node.js v16.13.2
  • npm v8.11.0
  • react v17.0.2
  • react-dom v17.0.2
  • typescript v4.7.4,
  • material-ui/core v4.12.3

create-react-app

1 .前回同様、'create-react-app'でプロジェクトを作成します。
今回は、typescriptを利用する為、'--template typescript'を指定します。

npx create-react-app combo-box --template typescript

2 .実行完了後、package.jsonを以下の通り編集します。
今回は、material-ui/core v4.12.3を使用する為、'react v17.0.2'を使用します。

'material-ui v4.12.3'はreact v18では使用出来ない。

  • react v17.0.2に変更
  • testing-libraryを削除(今回は不要な為)
package.json
{
  "name": "combo-box",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
-   "@testing-library/jest-dom": "^5.16.5",
-   "@testing-library/react": "^13.3.0",
-   "@testing-library/user-event": "^13.5.0",
+   "@material-ui/core": "^4.12.3",
    "@types/jest": "^27.5.2",
    "@types/node": "^16.11.48",
-   "@types/react": "^18.0.17",
+   "@types/react": "^17.0.0",
-   "@types/react-dom": "^18.0.6",
+   "@types/react-dom": "^17.0.0",
-   "react": "^18.2.0",
+   "react": "^17.0.2",
-   "react-dom": "^18.2.0",
+   "react-dom": "^17.0.2",
    "react-scripts": "5.0.1",
    "typescript": "^4.7.4",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

3 .以下ファイルを削除します。(testing-libraryを削除した為)

  • src/App.test.tsx

4 .'package-lock.json'及び'node_modules/'を削除してnpm installを実行します。
 ※プロジェクト配下にディレクトリを移動してから、実行するようにする。

npm install

5 .'src/index.tsx'を以下の通り修正します。

index.tsx
+import { render } from "react-dom";
-import React from 'react';
-import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

+const rootElement = document.getElementById("root");
+render(<App />, rootElement);

-const root = ReactDOM.createRoot(
-  document.getElementById('root') as HTMLElement
-);
-root.render(
-  <React.StrictMode>
-    <App />
-  </React.StrictMode>
-);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

6 .'npm start'を実行します。

npm start

無事にいつもの画面が表示されました。
WS000000.JPG

コンボボックス作成

今回は、'App.tsx'内にコンボボックスを作成しました。

  • 完成した画面
    都道府県のプルダウンを変更すると、その都道府県に紐づく市区町村のプルダウンが生成されます。
    WS000001.JPG

  • ソースコード

index.tsx
import { render } from "react-dom";
import './index.css';
import App from "./App";
import reportWebVitals from './reportWebVitals';

const rootElement = document.getElementById("root");
render(<App />, rootElement);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

※前回同様、INPUTデータはJSONで受け取ることを想定して作ってみました!

App.tsx
import React, { useState, useRef } from "react";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import InputLabel from "@material-ui/core/InputLabel";
import './App.css';

interface DataList {
  prefecturesId: string;
  prefecturesName: string;
  city: {
    cityId: string;
    cityName: string;
  }[];
}

interface ComboBoxItem {
  id: string;
  value: string;
}

const InputData = [
  {
    prefecturesId: "1",
    prefecturesName: "東京都",
    city: [
      {
        cityId: "1",
        cityName: "立川市",
      },
      {
        cityId: "2",
        cityName: "八王子市",
      },
      {
        cityId: "3",
        cityName: "調布市",
      },
      {
        cityId: "4",
        cityName: "青梅市",
      },
      {
        cityId: "5",
        cityName: "世田谷区",
      },
      {
        cityId: "6",
        cityName: "江東区",
      },
      {
        cityId: "7",
        cityName: "品川区",
      },
    ],
  },
  {
    prefecturesId: "2",
    prefecturesName: "神奈川県",
    city: [
      {
        cityId: "1",
        cityName: "横須賀市",
      },
      {
        cityId: "2",
        cityName: "横浜市",
      },
      {
        cityId: "3",
        cityName: "川崎市",
      },
      {
        cityId: "4",
        cityName: "逗子市",
      },
    ],
  },
];

// INPUT用JSONデータ作成
const Data = JSON.stringify(InputData);

const App = () => {
  // JSON形式のINPUTデータを取り出し
  const data = JSON.parse(Data);
  const inputData: DataList[] = Object.keys(data).map(function (key) {
    return data[key];
  });

  // 都道府県リストState管理
  const [prefecturesOptions] = useState<ComboBoxItem[]>(
    inputData.map((d) => {
      return {
        id: d.prefecturesId,
        value: d.prefecturesName,
      };
    })
  );
  // 都道府県リストで選択中の都道府県ID State管理
  const [selectedPrefecturesId, setSelectedPrefecturesId] = useState<string>(
    inputData[0].prefecturesId
  );
  // 都道府県(選択中)Ref管理
  const prefecturesOptionsRef = useRef(
    inputData
      .filter((d) => d.prefecturesId === selectedPrefecturesId)[0]
      .city.map((d) => {
        return {
          id: d.cityId,
          value: d.cityName,
        };
      })
  );

  // 市区町村リストState管理
  const [selectedCityId, setSelectedCityId] = useState(
    inputData[0].city[0].cityId
  );

  // 都道府県リスト変更時
  const onPrefecturesChangeHandler = (prefecturesId: string) => {
    //選択した都道府県をStateに設定
    setSelectedPrefecturesId(prefecturesId);

    //選択した都道府県の市区町村一覧
    const selectedCity = inputData.filter(
      (d) => d.prefecturesId === prefecturesId
    )[0].city;

    //選択した都道府県の市区町村リストの先頭を指定
    setSelectedCityId(selectedCity[0].cityId);

    //選択した都道府県の市区町村をRefに指定
    prefecturesOptionsRef.current = selectedCity.map((d) => {
      return {
        id: d.cityId,
        value: d.cityName,
      };
    });
  };

  return (
    <div className="div">
      <FormControl className="form-width">
        <InputLabel>都道府県</InputLabel>
        <Select
          defaultValue={prefecturesOptions[0].id}
          value={selectedPrefecturesId}
          onChange={(e) => {
            if (e.target.value !== undefined) {
              onPrefecturesChangeHandler(e.target.value as string);
            }
          }}
        >
          {prefecturesOptions.map((item) => (
            <MenuItem value={item.id} key={item.id}>
              {item.value}
            </MenuItem>
          ))}
        </Select>
      </FormControl>
      <div className="spacer"></div>
      <FormControl className="form-width">
        <InputLabel>市区町村</InputLabel>
        <Select
          defaultValue={"1"}
          value={selectedCityId}
          onChange={(e) => {
            if (e.target.value !== undefined) {
              setSelectedCityId(e.target.value as string);
            }
          }}
        >
          {prefecturesOptionsRef.current.map((item) => (
            <MenuItem value={item.id} key={item.id}>
              {item.value}
            </MenuItem>
          ))}
        </Select>
      </FormControl>
    </div>
  );
};

export default App;
App.css
/* 全体 */
.div {
  margin: 25px;
}

/* プルダウン間隔 */
.spacer {
  margin-top: 15px;
}

/* プルダウン幅 */
.form-width {
  width: 200px;
}

まとめ

今回、'useState','useRef'を実際に使ってみて使い方がイメージでき、
'Material-UI'についても、知見を深めることが出来ました。
Typescriptに関しては、型宣言が出来るのはやはり便利だと痛感しました。

また、本来、今回作成したコンボボックスは、'FormControl'タグ以降をコンポーネント化して利用するのが、
Reactの正しい書き方だと思います。※今回は、1ファイル("App.tsx")に記載しています。
コンポーネント化の粒度等も今後勉強していきたいと思いました。
※今回、参考にさせて頂きましたサイトの方はコンボボックスが、コンポーネント化されています。

参考資料

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?