はじめに
前回は、簡単な入力フォームを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を削除(今回は不要な為)
{
"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'を以下の通り修正します。
+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
コンボボックス作成
今回は、'App.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で受け取ることを想定して作ってみました!
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;
/* 全体 */
.div {
margin: 25px;
}
/* プルダウン間隔 */
.spacer {
margin-top: 15px;
}
/* プルダウン幅 */
.form-width {
width: 200px;
}
まとめ
今回、'useState','useRef'を実際に使ってみて使い方がイメージでき、
'Material-UI'についても、知見を深めることが出来ました。
Typescriptに関しては、型宣言が出来るのはやはり便利だと痛感しました。
また、本来、今回作成したコンボボックスは、'FormControl'タグ以降をコンポーネント化して利用するのが、
Reactの正しい書き方だと思います。※今回は、1ファイル("App.tsx")に記載しています。
コンポーネント化の粒度等も今後勉強していきたいと思いました。
※今回、参考にさせて頂きましたサイトの方はコンボボックスが、コンポーネント化されています。