質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

2回答

2024閲覧

React で依存関係のあるフォームの値設定方法がわからない

k_6363

総合スコア1

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/10/31 01:53

編集2021/10/31 02:13

前提・実現したいこと

React で依存関係がある、かつその初期値が非同期で取得される。というようなフォームの作成をしています。

例:
・地方セレクトボックス
・県セレクトボックス
・市セレクトボックス
上記のようなセレクトボックスがあり「地方の値によって県の値が変わる」「県の値によって市の値が変わる」というような依存関係を持っている。かつユーザー設定値が初期表示時には設定される。
(「東北」を選択すると「青森県」」「青森市」が自動設定される。ただしユーザーが「弘前市」に住んでいた場合、初期表示時は「東北」「青森県」「弘前市」となる)

上記のようなフォームを useEffect() useState() を使用し作成しようとしています。

発生している問題

ユーザー設定値を初期値として設定する方法がわかりません。
依存関係のあるフォームなので useEffect() の第二引数配列に依存値を設定し値決定するように作ろうと思ったのですが、その場合、初期値を設定すると useEffect() が作動してしまい、初期値を上書きしてしまいます。(例だと市の初期値は「紘前」としたいが「青森」になってしまう)

依存値設定と初期値設定両方が実現できる方法がありましたらご教授願います。
(実際にはさらにフォームがあり依存関係が複雑なため、より煩雑でない実装方法であると幸いです)

該当のソースコード

typescript

1const Home: NextPage = () => { 2 const [region, setRegion] = useState('関東') // 地方 3 useEffect(() => { 4 const setInitialValue = async () => { 5 // 非同期で初期値を取得して設定 6 setRegion('東北') 7 } 8 setInitialValue() 9 }, []) 10 const handleChangeRegion = (e: ChangeEvent<HTMLSelectElement>) => { 11 setRegion(e.target.value) 12 } 13 14 const [prefecture, setPrefecture] = useState('東京') // 都道府県 15 useEffect(() => { 16 switch (region) { 17 case '関東': 18 setPrefecture('東京') 19 break 20 case '関西': 21 setPrefecture('大阪') 22 break 23 case '東北': 24 setPrefecture('青森') 25 break 26 } 27 }, [region]) 28 useEffect(() => { 29 const setInitialValue = async () => { 30 // 非同期で初期値を取得して設定 31 setPrefecture('青森') 32 } 33 setInitialValue() 34 }, []) 35 const handleChangePrefecture = (e: ChangeEvent<HTMLSelectElement>) => { 36 setPrefecture(e.target.value) 37 } 38 39 const [city, setCity] = useState('渋谷') // 市区 40 useEffect(() => { 41 switch (prefecture) { 42 case '東京': 43 setCity('渋谷') 44 break 45 case '大阪': 46 setCity('大阪') 47 break 48 case '青森': 49 setCity('青森') 50 break 51 } 52 }, [prefecture]) 53 useEffect(() => { 54 const setInitialValue = async () => { 55 // 非同期で初期値を取得して設定 56 setCity('弘前') 57 } 58 setInitialValue() 59 }, []) 60 const handleChangeCity = (e: ChangeEvent<HTMLSelectElement>) => { 61 setCity(e.target.value) 62 } 63 64 return ( 65 <Box> 66 <Box color="gray"> 67 <Box display="flex"> 68 <label>地方</label> 69 <select value={region} onChange={handleChangeRegion}> 70 <option value="関東">関東</option> 71 <option value="関西">関西</option> 72 <option value="東北">東北</option> 73 </select> 74 </Box> 75 <Box display="flex"> 76 <label></label> 77 <select value={prefecture} onChange={handleChangePrefecture}> 78 <option value="東京">東京</option> 79 <option value="大阪">大阪</option> 80 <option value="青森">青森</option> 81 </select> 82 </Box> 83 <Box display="flex"> 84 <label></label> 85 <select value={city} onChange={handleChangeCity}> 86 <option value="渋谷">渋谷</option> 87 <option value="大阪">大阪</option> 88 <option value="青森">青森</option> 89 <option value="弘前">弘前</option> 90 </select> 91 </Box> 92 </Box> 93 </Box> 94 ) 95} 96 97export default Home

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自分ならこういうアプローチの仕方をするだろうと思う実装の方向性を回答します。

ざっくり要点と挙げると、以下です。

  • 地方(region)、都道府県(prefecture)、市区町村(city) の3つのプロパティを持ったオブジェクトの型を定義する。(仮に type TLocation とする。)
  • 上記の型TLocationの値でuseStateする。
  • ユーザーの設定値の取得時は、地方、都道府県、市区町村それぞれを個別に取得するのではなく、TLocation型の値として得られるようにする。

上記のサンプル実装を以下に挙げます。

(1) まず、サンプル実装の前提となるダミーデータや型の定義などを仮に実装しておきます。

typescript

1const REGIONS = ["関東", "関西", "東北"] as const; 2type TRegion = typeof REGIONS[number]; 3 4const PREFECTURES_BY_REGION: Record<TRegion, readonly string[]> = { 5 "関東": [ "東京", "神奈川" ], 6 "関西": [ "大阪", "兵庫" ], 7 "東北": [ "青森", '宮城'], 8} as const; 9 10const CITIES_BY_PREFECTURE: Record<string, readonly string[]> = { 11 "東京": [ "渋谷", "新宿" ], 12 "神奈川": [ "横浜", "相模原" ], 13 "大阪": [ "大阪", "尼崎" ], 14 "兵庫": [ "神戸", "姫路" ], 15 "青森": [ "青森", "弘前" ], 16 "宮城": [ "仙台", "石巻" ], 17} as const; 18 19type TPrefecture = keyof typeof CITIES_BY_PREFECTURE; 20type TCity = string

(2) 上記を使って、TLocation 型を作ります。

typescript

1type TLocation = { 2 region: TRegion; 3 prefecture: TPrefecture; 4 city: TCity; 5} 6 7const defaultLocation: TLocation = { 8 region: "関東", 9 prefecture: "東京", 10 city: "渋谷" 11} as const; 12 13

(3) ユーザーの設定値を取得するAPIを模した関数を作っておきます。(動作確認のため、2秒待たせます)

typescript

1const fetchUserLocation = async (): Promise<TLocation> => { 2 const sampleLocation: TLocation = { 3 region: "東北", 4 prefecture: "青森", 5 city: "弘前" 6 }; 7 return new Promise(resolve => { 8 setTimeout(() => { 9 resolve(sampleLocation); 10 }, 2000); 11 }); 12};

(4) 上記の(1)〜(3)の仮実装のもとで、以下はHome の修正案です。

tsx

1const Home: NextPage = () => { 2 const [location, setLocation] = useState<TLocation>(defaultLocation); 3 4 useEffect( () => { 5 const setUserLocation = async () => { 6 const userLocation = await fetchUserLocation(); 7 setLocation(userLocation); 8 } 9 setUserLocation(); 10 }, []); 11 12 const handleChangeRegion = (e: ChangeEvent<HTMLSelectElement>) => { 13 const region = e.target.value as TRegion; 14 const prefecture = PREFECTURES_BY_REGION[region][0] as TPrefecture; 15 const city = CITIES_BY_PREFECTURE[prefecture][0]; 16 setLocation({ region, prefecture, city }); 17 } 18 19 const handleChangePrefecture = (e: ChangeEvent<HTMLSelectElement>) => { 20 const prefecture = e.target.value as TPrefecture; 21 const city = CITIES_BY_PREFECTURE[prefecture][0]; 22 setLocation({ ...location, prefecture, city }); 23 } 24 25 const handleChangeCity = (e: ChangeEvent<HTMLSelectElement>) => { 26 setLocation({ ...location, city: e.target.value }); 27 } 28 29 return ( 30 <Box> 31 <Box color="gray"> 32 <Box display="flex"> 33 <label>地方</label> 34 <select value={location.region} onChange={handleChangeRegion}> 35 {REGIONS.map(e => <option value={e} key={e}>{e}</option>)} 36 </select> 37 </Box> 38 <Box display="flex"> 39 <label></label> 40 <select value={location.prefecture} onChange={handleChangePrefecture}> 41 {PREFECTURES_BY_REGION[location.region].map(e => <option value={e} key={e}>{e}</option>)} 42 </select> 43 </Box> 44 <Box display="flex"> 45 <label></label> 46 <select value={location.city} onChange={handleChangeCity}> 47 {CITIES_BY_PREFECTURE[location.prefecture].map(e => <option value={e} key={e}>{e}</option>)} 48 </select> 49 </Box> 50 </Box> 51 </Box> 52 ) 53} 54 55export default Home 56 57

投稿2021/10/31 13:45

編集2021/10/31 14:48
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k_6363

2021/11/02 10:30

今回は useRef によるフラグで管理する方式が仕様にマッチしていたためそちらを採用しましたが、とても参考になりました。 ご回答ありがとうございましたmm
guest

0

ベストアンサー

スマートでは無いかもしれませんが、依存関係を有効にするか否かを別変数で管理するのが手っ取り早いように思えます。
参考にしてみて下さい。

react

1 2const Home: NextPage = () => { 3 // ------------------ 依存関係を有効にするかのRefオブジェクト ------------------ // 4 const dependencyEffective = useRef(true); 5 const enableDependencies = () => (dependencyEffective.current = true); 6 const disableDependencies = () => (dependencyEffective.current = false); 7 8 // ------------------ 地方 ------------------ // 9 const [region, setRegion] = useState("関東"); 10 11 const handleChangeRegion = (e: ChangeEvent<HTMLSelectElement>) => { 12 setRegion(e.target.value); 13 enableDependencies(); // 通常の更新では依存関係を有効にする 14 }; 15 16 // ------------------ 都道府県 ------------------ // 17 const [prefecture, setPrefecture] = useState("東京"); 18 19 useEffect(() => { 20 if (!dependencyEffective.current) return; 21 22 switch (region) { 23 case "関東": 24 setPrefecture("東京"); 25 break; 26 case "関西": 27 setPrefecture("大阪"); 28 break; 29 case "東北": 30 setPrefecture("青森"); 31 break; 32 } 33 }, [region]); 34 35 const handleChangePrefecture = (e: ChangeEvent<HTMLSelectElement>) => { 36 setPrefecture(e.target.value); 37 enableDependencies(); // 通常の更新では依存関係を有効にする 38 }; 39 40 // ------------------ 市区 ------------------ // 41 const [city, setCity] = useState("渋谷"); 42 43 useEffect(() => { 44 if (!dependencyEffective.current) return; 45 46 switch (prefecture) { 47 case "東京": 48 setCity("渋谷"); 49 break; 50 case "大阪": 51 setCity("大阪"); 52 break; 53 case "青森": 54 setCity("青森"); 55 break; 56 } 57 }, [prefecture]); 58 59 const handleChangeCity = (e: ChangeEvent<HTMLSelectElement>) => { 60 setCity(e.target.value); 61 enableDependencies(); // 通常の更新では依存関係を有効にする 62 }; 63 64 // ------------------ 初期値のセット ------------------ // 65 useEffect(() => { 66 const setInitialValue = async () => { 67 // 非同期で初期値を取得して設定 68 setRegion("東北"); 69 setPrefecture("青森"); 70 setCity("弘前"); 71 disableDependencies(); // 初期値のセットの場合(初期値に限らずシステム側でセットする場合)は依存関係を無効にする 72 }; 73 setInitialValue(); 74 }, []); 75 // (初期値のセットはまとめた方が良いと思います) 76 77 return ( 78 <Box> 79 <Box color="gray"> 80 <Box display="flex"> 81 <label>地方</label> 82 <select value={region} onChange={handleChangeRegion}> 83 <option value="関東">関東</option> 84 <option value="関西">関西</option> 85 <option value="東北">東北</option> 86 </select> 87 </Box> 88 <Box display="flex"> 89 <label>県</label> 90 <select value={prefecture} onChange={handleChangePrefecture}> 91 <option value="東京">東京</option> 92 <option value="大阪">大阪</option> 93 <option value="青森">青森</option> 94 </select> 95 </Box> 96 <Box display="flex"> 97 <label>市</label> 98 <select value={city} onChange={handleChangeCity}> 99 <option value="渋谷">渋谷</option> 100 <option value="大阪">大阪</option> 101 <option value="青森">青森</option> 102 <option value="弘前">弘前</option> 103 </select> 104 </Box> 105 </Box> 106 </Box> 107 ); 108}; 109 110export default Home; 111 112

投稿2021/10/31 07:08

k4a

総合スコア983

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

k_6363

2021/11/02 10:27

チェンジイベントハンドラに寄せた実装にするか迷っていましたが、ご提示いただいた方式の方がシンプルでわかりやすいため採用させていただきました。 ご回答ありがとうございましたmm
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問