Context

Context

2020/11/17

利用persistent state(例如:SecureStore) 可以在手機裡儲存資料,不過,從persistent state裡讀入資料之後,是不是可以將登入狀態跟所有的元件分享? 在react native裡可以利用context來達成這樣的效果,或者,使用更複雜的Redux來達成這樣的效果,我們先來試試看Context。

首先,我們可以先利用state變數(isSignedIn)來決定在tab navigator可顯示內容

function App() {

const [count, setCount] = useState(10);

const [isSignedIn, setIsSignedIn] = useState(false);

當isSignedIn是false的時候,就會看到Sign In及Sign Up。

<NavigationContainer>

<Tab.Navigator>

{isSignedIn?(

<>

<Tab.Screen name="Person" component={PersonList} />

<Tab.Screen name="Product" component={ProductList} />

</>

)

:(

<>

<Tab.Screen name="Sign In" component={SignIn} />

<Tab.Screen name="Sign Up" component={SignUp} />

</>

)

}

</Tab.Navigator>

</NavigationContainer>

那如果要讓PersonList收到isSignedIn,通常就可以使用props,因為我們使用Navigator,所以,要透過initialParams。只是,這樣子要一層一層的傳遞變數,有點麻煩。在react裡提供了Context,利用Context可以創造類似全域變數的效果。首先,先利用createContext,產生一個context物件。

createContext

/src/account/AuthContext.js

import React from 'react';

export const AuthContext = React.createContext({

isSignedIn: false

})

context.Provider

在/App.js裡先import AuthContext

import {AuthContext} from './src/account/AuthContext';

接下來,將需要使用AuthContext利用AuthContext.Provider包起來,並把App裡isSignedIn的值透過傳給AuthContext裡的isSignedIn:

<NavigationContainer>

<AuthContext.Provider value={{isSignedIn: isSignedIn}}>

<Tab.Navigator>


{isSignedIn?(

<>

<Tab.Screen name="Person" component={PersonList} />

<Tab.Screen name="Product" component={ProductList} />

</>

)

:(

<>

<Tab.Screen name="Sign In" component={SignIn} />

<Tab.Screen name="Sign Up" component={SignUp} />

</>

)

}

</Tab.Navigator>

</AuthContext.Provider>

</NavigationContainer>

useContext

在PersonList裡,先import useContext及AuthContext

import React, {useState, useEffect, useContext} from 'react';
import {AuthContext} from '../account/AuthContext';

在PersonList裡,利用useContext hook取得AuthContext裡的值。

export default function PersonList() {

const get_url=url+"?maxRecords=50&view=Grid%20view";

const [persons, setPersons] = useState([]);

const [modalVisible, setModalVisible] = useState(false);

const [selectedId, setSelectedId] = useState(null);

const [person, setPerson] = useState({

Name:"",

City:"",

Age:0}

);//temp variable for edit

const [isLoading, setIsLoading] = useState(true);

const authContext = useContext(AuthContext);

在useEffect裡,可以看到取得儲存在Context裡的資料。

useEffect(() => {

console.log("isSignedIn in PersonList:"+authContext.isSignedIn);

setIsLoading(true);

console.log("fetchData");

console.log(`url:${url}`);

fetchData();

},[modalVisible]);

試試看,把App.js裡的isSignedIn預設值改為true,然後,點選Person就可以看到PersonList成功的取得isSignedIn。

function App() {

const [count, setCount] = useState(10);

const [isSignedIn, setIsSignedIn] = useState(true);

記得改回去:

function App() {

const [count, setCount] = useState(10);

const [isSignedIn, setIsSignedIn] = useState(false);

更動context

那要怎麼更動Context裡的值呢? 先在AuthContext留一個空的方法,其實這裡的變數值及方法都會在設定Provider時覆蓋。

/src/account/AuthContext.js

import React from 'react';

export const AuthContext = React.createContext({

isSignedIn: false,

setStatus: ()=>{}

})

//isSignedIn & setStatus will be replaced in App.js

在App.js裡,把App的setIsSignedIn取代AuthContext裡的setStatus。

return (

<NavigationContainer>

<AuthContext.Provider value={{isSignedIn: isSignedIn, setStatus:setIsSignedIn }}>

在SignIn.js裡

import {AuthContext} from '../account/AuthContext';

利用useContext取得authContext

export default function SignIn() {

const [email, setEmail] = useState("");

const [password, setPassword] = useState("");

const [message, setMessage] = useState("");

const authContext = useContext(AuthContext);

呼叫authContext裡的setStatus,記得,事實上是呼叫App的setIsSignedIn。

async function signIn(){

try {

const res= await firebase.auth()

.signInWithEmailAndPassword(email, password);

//console.log('User login successfully!');

const loginString = JSON.stringify({email:email, password:password});

await SecureStore.setItemAsync("login", loginString);

setEmail('');

setPassword('');

setMessage('');

//console.log("account saved");

authContext.setStatus(true);

}

catch(error){

setMessage(error.message);

}
};