複数画面で認証済みのユーザー情報を引き回したいなあと思ってどうやったらいいか調べたところ、画面間をこえてグローバルに値を保持することが出来る、React標準のContext APIというものがあることが分かったので試してみる。
このContext APIそんなに使い勝手は良くないみたいで、redux
とかrecoil
でこの辺面倒見てくれるとかって噂は聞いているが、まあ標準APIで実現出来るならそれもそれで良いかな。
とりあえず画面遷移を実装
以前の記事で画面遷移出来るところまで実装したので、これをベースに進める。
Expo + ReactNative + Firestoreでラーメン一覧から詳細画面への画面遷移を実装する | もふもふ技術部
react-navigation
関係の必要なライブラリをインストールする。
$ expo install react-navigation react-navigation-stack react-navigation-tabs react-native-gesture-handler react-native-reanimated react-native-safe-area-context @react-native-community/masked-view
画面遷移できた。
Contextを導入する
この辺を参考にした。
React Context API と useContext() の使い方 | gotohayato.com
公式チュートリアルでは1ファイルに記述するサンプルが載っていて、実際の使い方とはかけ離れていてわかりづらい。たぶんContextは外部に切り出さないとと複数箇所で呼び出せないと思う。
以下コード、tsやらjsやらが混ざったままになっててすんません。動作することを確認する目的だったので手を抜きました。
App.tsx
import React, { useEffect, useState } from "react" import { StyleSheet } from 'react-native'; import { createAppContainer } from 'react-navigation'; import { createStackNavigator } from 'react-navigation-stack'; import Page1Screen from './screens/Page1'; import Page2Screen from './screens/Page2'; import { UserContext, User } from './screens/UserContext' const MainStack = createStackNavigator( { Page1: Page1Screen, Page2: Page2Screen, } ) const AppContainer = createAppContainer(MainStack) export default function App() { const [user, setUser] = useState({} as User); useEffect(() => { // 非同期のサインイン処理をsetTimeoutで模倣 setTimeout(() => { setUser({ uid: '1233455', name: 'Taro Yamada' } as User) }, 2000) }, []) return ( <UserContext.Provider value={ user }> <AppContainer /> </UserContext.Provider> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, });
screens/Page1.js
import React, { useContext } from 'react'; import { Text,View,Button } from 'react-native'; import { UserContext } from './UserContext' export default function Page1Screen(props) { const user = useContext(UserContext) return ( <View> <Text>Page2</Text> <Text>user id: {user.uid}</Text> <Text>user name: {user.name}</Text> <Button title="go to Page1" onPress={() => { props.navigation.navigate('Page1') }} /> </View> ) }
screens/Page2.js
import React, { useContext } from 'react'; import { Text,View,Button } from 'react-native'; import { UserContext } from './UserContext' export default function Page1Screen(props) { const user = useContext(UserContext) return ( <View> <Text>Page2</Text> <Text>user id: {user.uid}</Text> <Text>user name: {user.name}</Text> <Button title="go to Page1" onPress={() => { props.navigation.navigate('Page1') }} /> </View> ) }
screens/UserContext.ts
import { createContext } from "react" export interface User { uid: string name: string } export const UserContext = createContext({} as User)
サインイン(を模倣した)処理後にContextにユーザー情報を保持して、Page1とPage2で、ユーザーのIDと名前を表示するサンプル。
Contextを更新する
子コンポーネントで親コンポーネントから受け取ったContextを更新するにはどうすればいいのだろうか?
実際にこういうケース発生しそうなので調べてみたところ、ContextにContext内のユーザー情報を更新する関数(setUser()
)を含めてしまおうっていうアプローチがあるらしいので、その方式でやってみる。
App.tsx
import React, { useEffect, useState } from "react" import { StyleSheet } from 'react-native'; import { createAppContainer } from 'react-navigation'; import { createStackNavigator } from 'react-navigation-stack'; import Page1Screen from './screens/Page1'; import Page2Screen from './screens/Page2'; import { UserContext, User } from './screens/UserContext' const MainStack = createStackNavigator( { Page1: Page1Screen, Page2: Page2Screen, } ) const AppContainer = createAppContainer(MainStack) export default function App() { const [user, setUser] = useState({} as User); useEffect(() => { // 非同期のサインイン処理をsetTimeoutで模倣 setTimeout(() => { setUser({ uid: '1233455', name: 'Taro Yamada' } as User) console.log('signedIn!') }, 2000) }, []) return ( <UserContext.Provider value={{ user: user, setUser: setUser }}> <AppContainer /> </UserContext.Provider> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, });
screens/Page1.js
import React, { useContext } from 'react'; import { Text,View,Button } from 'react-native'; import { UserContext } from './UserContext' export default function Page1Screen(props) { const context = useContext(UserContext) return ( <View> <Text>Page1</Text> <Text>user id: {context.user.uid}</Text> <Text>user name: {context.user.name}</Text> <Button title="go to Page2" onPress={() => { props.navigation.navigate('Page2') }} /> </View> ) }
screens/Page2.js
import React, { useContext } from 'react'; import { Text,View,Button } from 'react-native'; import { UserContext } from './UserContext' export default function Page1Screen(props) { const context = useContext(UserContext) return ( <View> <Text>Page2</Text> <Text>user id: {context.user.uid}</Text> <Text>user name: {context.user.name}</Text> <Button title="go to Page1" onPress={() => { props.navigation.navigate('Page1') }} /> <Button title="chage user value" onPress={() => { // 別のユーザー情報でuserを更新する const user = { uid: '9999999', name: 'Jiro Saito' } context.setUser(user) }} /> </View> ) }
screens/UserContext.ts
import { createContext } from "react" export interface User { uid: string name: string } export const UserContext = createContext({})
こんな感じで、Page2でユーザー情報を更新すると自動的にPage1の情報も更新される。ふむ、やりたいことは大体これで出来た気がする。一方でちょっとContext APIはなんとでも書けてしまう危うさを感じるっちゃ感じる。そのうち他のライブラリも試してみよう。