前提・実現したいこと
Cloud Firestoreと連携してリストを表示するアプリを作っています。
現在、ReactNavigationのStackNavigatorを使ってヘッダーを作っているところです。
ヘッダー右部にスイッチとアイコンを設置する予定で、機能は
スイッチ:リストのモードを切り替える(仮にAモード・Bモードとします)
アイコン:アプリのログアウト(Firestoreのリアルタイム連携を停止)
です。
画面の概形は以下のような感じです。青部分がリストで、モードによって表示内容が変わります。
発生している問題・エラーメッセージ
スイッチをタップしたとき、リストは切り替わるがスイッチのON/OFFが正常に切り替わりません。
例えば、Aモード(OFF)からBモード(ON)に切り替えるとき
- スイッチをタップする
- スイッチがONになる
- 少し時間をおいて、リストがBモードになる
- スイッチがOFFに戻る(リストはBモードを維持)
という挙動です。
該当のソースコード
javascript
1import React, { useEffect, useState } from 'react'; 2import { View, StyleSheet, Text, Alert } from 'react-native'; 3import { MaterialCommunityIcons } from '@expo/vector-icons'; 4import { Switch } from 'react-native-paper'; 5import { getAuth, onAuthStateChanged } from '@firebase/auth'; 6import { 7 getFirestore, 8 collection, 9 onSnapshot, 10 where, 11 orderBy, 12 limit, 13 query, 14} from 'firebase/firestore'; 15 16// ローディング中を表示するコンポーネント 17import Loading from '../components/Loading'; 18// 画面概形の青部分に当たるリスト 19import ShareList from '../components/ShareList'; 20// アプリからログアウトするボタン 21import LogoutButton from '../components/LogoutButton'; 22 23 24export default function ShareListScreen({ navigation }) { 25 // Aモードのリスト(list:リストデータ, loading:ローディン中ならtrue) 26 const [shareListState, setShareListState] = useState({ 27 list: [], 28 loading: false, 29 }); 30 // Bモードのリスト(list:リストデータ, loading:ローディン中ならtrue) 31 const [okawariListState, setOkawariListState] = useState({ 32 list: [], 33 loading: false, 34 }); 35 // Aモードならfalse, Bモードならtrue 36 const [listModeSwitched, setListModeSwitched] = useState(true); 37 38 // スイッチをタップした時の処理 39 const toggleSwitch = () => { 40 setListModeSwitched((prev) => !prev); 41 }; 42 43 useEffect(() => { 44 // 各モードのリストをローディング中に設定 45 setShareListState((prev) => ({ ...prev, loading: true })); 46 setOkawariListState((prev) => ({ ...prev, loading: true })); 47 48 // クリーンアップ関数 49 const cleanupFuncs = { 50 auth: () => {}, 51 shareList: () => {}, 52 okawariList: () => {}, 53 }; 54 55 cleanupFuncs.auth = onAuthStateChanged(getAuth(), (user) => { 56 if (user) { 57 // Aモードのリストのスナップショット取得(取得完了時にローディングをfalseに設定) 58 cleanupFuncs.shareList = onSnapshot( ... ); 59 // Bモードのリストのスナップショット取得(取得完了時にローディングをfalseに設定) 60 cleanupFuncs.okawariList = onSnapshot( ... ); 61 } 62 63 // ヘッダーオプション 64 navigation.setOptions({ 65 headerRight: () => ( 66 <View style={styles.headerRightContainer}> 67 <View style={styles.listModeSwitchFrame}> 68 <MaterialCommunityIcons 69 name='rice' 70 size={24} 71 color='#000000' 72 style={styles.listModeSwitchIcon} 73 /> 74 <Switch value={listModeSwitched} onValueChange={toggleSwitch} /> 75 </View> 76 <View style={styles.lougoutButtonFrame}> 77 {/* ログアウト時にクリーンアップ関数を実行させるため、propsで渡している */} 78 <LogoutButton cleanupFuncs={cleanupFuncs} /> 79 </View> 80 </View> 81 ), 82 }); 83 }); 84 85 return () => { 86 cleanupFuncs.auth(); 87 cleanupFuncs.shareList(); 88 cleanupFuncs.okawariList(); 89 }; 90 }, []); 91 92 if (listModeSwitched) { 93 // Bモードで表示 94 } else { 95 // Aモードで表示 96 } 97} 98 99const styles = StyleSheet.create({ ... }); 100
考えられる原因
useEffect内でヘッダーオプションを設定しているのが原因だと考えています。
スイッチがもとに戻ってしまう現象は、DOMマウント後1度だけ実行されるuseEffect時のスイッチの状態(
<Switch value={false} onValueChange={toggleSwitch} />
)が保持されているためと予想しています。
試したこと
・クリーンアップ関数をuseStateで保持して、toggleSwitch
関数内でヘッダーオプションを再設定
⇒改善されず
・useEffectの実行をlistModeSwitched
依存に変更
⇒スイッチを切り替えるたびにスナップショット取得が実行されて、動作が非常に遅くなってしまう
質問したいこと
①ヘッダーオプションをuseEffectで定義する状態のまま、スイッチの動作が想定通りになる方法があればお聞きしたいです。
②headerRight
を一度で定義せずあとからコンポーネントを追加できるような方法はあるのでしょうか。
(useEffectでログアウトボタン追加、他の部分でスイッチ追加 のように)
補足情報(FW/ツールのバージョンなど)
react : 17.0.1
react-native : 0.64.3
@react-navigation/native : 6.0.6
@react-navigation/stack : 6.0.11
react-native-paper : 4.11.1
あなたの回答
tips
プレビュー