Reputation: 333
Houston, we have a problem.
I never quite understood how Redux works. When the time came to do the authentication flow for my react native app, I found a tutorial that used a reducer, copied it, and it worked... It was always very confusing to me why the code worked since I don't see where I pass in the "loginState" variable into the context... but it was working (on ios) so I let it be.
But today, as I was trying the web version of the react native app it became a problem. For some reason I dont understand either, it is acceptable to declare a variable without the "const" in react native ios/android but not web. If to fix that i declare the redux state with type declaration (like shown here) it is not available to nested functions anymore:
**const** [loginState, dispatch] = React.useReducer(LoginReducer, {isLoading: false, isSignout: false, userToken: null, isVerified: false})
Please please please help me understand
Here is my App.js:
import * as React from 'react';
import LoginReducer from './Reducer/LoginReducer';
import AppContainer from './Navigators/AppNavigationContainer.js'
import {loadLocale, strings2} from './assets/locales/i18n'
import {AppLoading} from 'expo'
global.AuthContext = React.createContext();
global.LanguageContext = React.createContext({
language: 'es',
setLanguage:()=>{}
})
export default function App ({navigation}) {
//LanguageContext
const [languageReady, setLanguageReady] = React.useState(false);
const [language,setLanguage] = React.useState('es');
//Load language
const initLang = async () => {
const currentLanguage = await loadLocale()
setLanguage(currentLanguage)
};
[loginState, dispatch] = React.useReducer(LoginReducer, {isLoading: false, isSignout: false, userToken: null, isVerified: false})
global.authContext = React.useMemo(() => ({
signIn: () => {
dispatch({ type: 'SIGN_UP' })
dispatch({ type: 'SIGN_IN', token: 'dummy-token' });
AsyncStorage.setItem('userToken', 'dummy-token' )
},
signOut: () => {
AsyncStorage.removeItem('userToken')
dispatch({ type: 'SIGN_OUT' })
},
signUp: () => {
dispatch({ type: 'SIGN_IN', token: 'dummy-token' })
AsyncStorage.setItem('userToken', 'dummy-token')
},
}),[]);
return(
<>
{languageReady ? (
<LanguageContext.Provider value={{language, setLanguage}}>
<AppContainer/>
</LanguageContext.Provider>
) : (
<AppLoading
startAsync={initLang()}
onFinish={setLanguageReady(true)}/>
)}
</>
)
}
And here is my App Navigator where the redux state "loginState" should be available to do the authentication flow.
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import LoadingScreen from '../Screens/LoadingScreen.js'
import SignInScreen from '../Screens/SignInScreen.js'
import VerifyEmail from '../Screens/VerifyEmail.js'
import MainAppTab from './MainAppTab.js'
const Stack = createStackNavigator();
export default function AppContainer () {
return (
<AuthContext.Provider value={global.authContext}>
<NavigationContainer>
<Stack.Navigator
initialRouteName='SignIn'
headerMode='none'
>
{loginState.isLoading ? (<>
<Stack.Screen name='Loading' component={LoadingScreen}/>
</>) : loginState.userToken == null ? (<>
<Stack.Screen name='SignIn' component={SignInScreen}/>
</>) : loginState.isVerified === false ? (<>
<Stack.Screen name='Verify' component={VerifyEmail}/>
</>) : (
<Stack.Screen name='Main' component={MainAppTab}/>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
)
}
Upvotes: 0
Views: 185
Reputation: 699
If you do not declare a variable with const
, var
, or let
, then it will reside in the global scope. That is why your state and dispatch function are available everywhere. const
, var
, let
, and not declaring a keyword all have different scopes. Read more about JavaScript scopes on this question, the mozilla developer site, and w3schools.
To make the state and dispatch function available elsewhere, you need to pass the values to the component it needs to go to. In your case, you need to pass them to your AppContainer component.
Here is an example below:
App.js:
import React from 'react';
import TestAppContainer from "./TestAppContainer";
const LOGIN = "LOGIN";
// This dummy reducer just returns the current state.
// Your reducer probably does something useful :)
const TestAppReducer = (state, action) => {
switch (action.type) {
case LOGIN:
return state;
default:
return state;
}
};
export default function App() {
const [loginState, dispatch] = React.useReducer(TestAppReducer, {isLoading: false, isSignout: false, userToken: null, isVerified: false})
return (
<TestAppContainer loginState={loginState} dispatch={dispatch} />
);
}
TestAppContainer.js:
import React from "react";
import {View, Text, Button} from "react-native";
const TestAppContainer = props => {
const { loginState, dispatch } = props;
const onPressHandler = () => {
console.log(loginState);
console.log(dispatch !== undefined);
}
return (
<View style={{flex: 1, justifyContent: "center", alignItems: "center"}}>
<Text>Test App WOW!</Text>
<Button title={"Press me."} onPress={onPressHandler} />
</View>
);
}
export default TestAppContainer;
Upvotes: 1