React native wait until user is logged in before navigating screens - react-native

I have a function that retrieves a token from SecureStore in react-native
export const isUserLoggedIn = async () =>{
return await SecureStore.getItemAsync('jwtToken') ? true : false
}
This is my navigator:
function RootNavigator() {
const [isLoggedIn, setIsLoggedIn] = useState(false)
console.log("isUserLoggedIn()", isUserLoggedIn());
(async () => {
const someBoolean = await isUserLoggedIn()
console.log("inside then")
setIsLoggedIn(someBoolean)
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isLoggedIn || <Stack.Screen name="Root" component={WelcomeScreen} />}
{isLoggedIn && <Stack.Screen name="InvestorProfileQuiz" component={InvestorProfileQuizScreen} />}
{isLoggedIn || <Stack.Screen name="AppTour" component={AppTourScreen} />}
{isLoggedIn || <Stack.Screen name="Login" component={LoginScreen} />}
{isLoggedIn || <Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: 'Oops!' }} />}
</Stack.Navigator>
);
})()
console.log("LOGGED IN STATE:", isLoggedIn)
The issue is that the LOGGED_IN_STATE is logged before the inside then console.log which means the code isn't blocking and waiting for the isUserLoggedIn() to resolve. isUserLoggedIn() returns a promise because it's an async await function, but do I wait for it to resolve before rendering the Stack Navigator? I in short want a logged in user to have access to certain screens and not others. What am I doing wrong?

I usually handle logged in state with something like this:
function RootNavigator() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
// Passing an empty array as second argument
// makes useEffect behave like componentDidMount
useEffect(() => {
// Wrap logic in the async init function
// since the useEffect callback can't be async
async function init() {
const someBoolean = await isUserLoggedIn();
setIsLoggedIn(someBoolean);
}
init();
}, []);
// Return null or your logged out screen component here.
if (!isLoggedIn) {
return null;
}
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="Root" component={WelcomeScreen} />}
<Stack.Screen
name="InvestorProfileQuiz"
component={InvestorProfileQuizScreen}
/>
<Stack.Screen name="AppTour" component={AppTourScreen} />
<Stack.Screen name="Login" component={LoginScreen} />(
<Stack.Screen
name="NotFound"
component={NotFoundScreen}
options={{title: 'Oops!'}}
/>
</Stack.Navigator>
);
}
In your code console.log("LOGGED IN STATE:", isLoggedIn) does not wait because the async await is incapsulated in the IIFE (https://developer.mozilla.org/en-US/docs/Glossary/IIFE) function scope.

Related

asyncstorage not working in build mode in react native

in debug mode asyncstorage working perfectly fine but when build apk with this command
gradlew assembleRelease -x bundleReleaseJsAndAssets
the apk build perfectly but i want to open it show me the error appstop in my phone
any help regarding this will be very appriciated
Try this.
import AsyncStorage from '#react-native-async-storage/async-storage';
export default function App()
{
const [aldreadyLaunched, setaldreadyLaunched] = useState(true)
useEffect(() => {
AsyncStorage.getItem("alreadyLaunched").then((value) => {
if (value == "false") {
let parsedValue = JSON.parse(value)
setaldreadyLaunched(parsedValue)
}
})
},[])
return (<>
display introductory screen when the already launched state is true and when the false display login or directly main screen
</>)
}
Try this .....
const [loading, setLoading] = useState(true);
const [isFirstTimeLoad, setIsFirstTimeLoad] = useState(false);
const checkForFirstTimeLoaded = async () => {
const result = await AsyncStorage.getItem('isFirstTimeOpen');
console.log('result:', result);
if (result == null) {
setIsFirstTimeLoad(true);
setLoading(false);
} else {
setIsFirstTimeLoad(false);
setLoading(false);
}
};
if (loading)
return (
<View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size={'large'} color={'black'}/>
</View>
);
if (isFirstTimeLoad)
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="OnboardingScreen"
screenOptions={{
headerShown: false,
// header: () => <MainHeader />,
}}>
<Stack.Screen name="OnboardingScreen" component={OnBoardingScreen} />
<Stack.Screen name="login" component={Login} />
<Stack.Screen name="home" component={Home} />
<Stack.Screen name="register" component={Register} />
<Stack.Screen name="mobileverify" component={MobileVerify} />
<Stack.Screen name="listscreen" component={ListData} />
</Stack.Navigator>
</NavigationContainer>
);
if (!isFirstTimeLoad) return <Login />;

How can i notify to App for what reload the app to show another screen?

I am in login screen i pressed the button login so
that button have this action
store.dispatch({
type: 'login',
payload: {
name: email.value
},
})
on the reducers change the value
export default function (state, action) {
switch (action.type) {
case 'login':
return {
...state,
user: action.payload,
isLogged:true
}
default:
return newState
}
}
now i have to notify to App.js for the new change because that is depend to show the stack.screen
export default function App() {
return (
<Provider store={store} theme={theme}>
<Tabs store={store} />
</Provider>
)
}
How can i notify to App for what reload the app to show another screen?
but never my component is updated, i was trying to update my component using useEffect but not
function Tabs ({ user }) {
const {
data: { isLogged },
} = useSelector((state) => state)
useEffect(() => {
console.log(1234546)
}, [isLogged, user])
return (
<Stack.Navigator >
{ !store.getState().data.isLogged ?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/> :
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>}
</Stack.Navigator>
)
When working with States Or Redux We don't have to tell the app about when to re-render the JSX code.
It will be re-renders automatically whenever the change occurs in redux or state.
But, We must have to be assured that we are not mutating the data in our state or redux, We always have to assign a new object to state or redux.
You can try the below changes in your code,
function Tabs ({ user }) {
const { isLogged } = useSelector((state) => state?.data)
return (
<Stack.Navigator >
{
isLogged
?
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
:
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
}
</Stack.Navigator>
)
}
App navigator need to subscribe isLogged state then rerender accordingly.
import { useEffect } from "react";
import { useSelector } from "react-redux";
const App = () => {
const isLogged = useSelector((state) => state.isLogged);
// Track authentication state
useEffect(() => {}, [isLogged]);
return (
<Stack.Navigator>
{!isLogged ? (
<Stack.Screen
name="loginScreen1"
component={HomeScreen}
options={{ headerShown: false }}
/>
) : (
<Stack.Screen
name="main"
component={MyTabs}
options={{ headerShown: false }}
/>
)}
</Stack.Navigator>
);
};

How to navigate to other screen after the whole app has loaded?

Good day! Currently learning Nav 5 and so far this is what I have tried. I want to navigate to other screens depending on the result of the Firebase login check.
But what happens is the app renders 3times in the Splash screen, what I want is the 3rd or the final data. How can I know when the app is done re-rendering stuff.
This is what I have tried and I am fairly new to React navigation hopefully somebody could point me on how to do this properly. Thank you!
APP.JS
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(user => {
let logged = false
if (user) {
logged = true
firebase.firestore().collection('users')
.doc(user.uid)
.get()
.then(firestoreDocument => {
if (!firestoreDocument.exists) {
return
}
const user = firestoreDocument.data()
console.log("subs data", user)
setUser(user)
})
.catch(error => {
alert(error)
})
} else {
setUser(null)
logged = false
}
setIsLoggedIn(logged)
setIsLogIncheckDone(true)
})
return subscriber // unsubscribe on unmount
}, [])
const getSomething = async () => {
const data = {
isLoggedIn, user, isLogInCheckDone
}
return data
}
if (!isLogInCheckDone) {
return <SplashScreen />
}
return (
<AppContext.Provider value={{ GetSomething: getSomething }}>
<ThemeContextProvider>
<ThemeContextConsumer>
{context =>
(
<NavigationContainer>
<Stack.Navigator theme={context.theme ? "dark" : "light"} >
<Stack.Screen name={"Splash"} component={SplashScreen} options={{ headerShown: false }} />
<Stack.Screen name={"Common"} component={CommonStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Auth"} component={AuthStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Dashboard"} component={DashboardStack} options={{ headerShown: false }} />
<Stack.Screen name={"Verify"} component={VerificationStackNav} options={{ headerShown: false }} />
<Stack.Screen name={"Wizard"} component={ProfileWizardStack} options={{ headerShown: false }} />
</Stack.Navigator>
<Toast ref={(ref) => Toast.setRef(ref)} />
</NavigationContainer>
)
}
</ThemeContextConsumer>
</ThemeContextProvider>
</AppContext.Provider>
)
}
SPLASHSCREEN.JS
export default function SplashScreen({ navigation, route }) {
const appContext = useContext(AppContext)
useEffect(() => {
const newData = appContext?.GetSomething()
console.log("Splashy", newData)
//TODO Navigate here
}, [appContext])
return (
<View style={styles.container}>
<LottieView source={newSource} autoPlay loop />
</View>
)
}
You can use the logged state in your app.js to render what you want. Try something like this:
return (
<NavigationContainer >
{logged ? <Screens you want to render if the user is logged /> : <SplashScreen />}
</NavigationContainer>
);

React navigation 5 stack navigator and drawer navigator together

Basically I am from react-navigation v4, I am familiar handling the same with SwitchNavigator as bridge, but v5 does no support for SwitchNavigator, so bit struggling to understand the below implementation.
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
function AuthenticationStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Home" screenOptions={{headerShown: false}}>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Registration" component={Registration} />
</Stack.Navigator>
);
}
function UserStack({ navigation }) {
return (
<Stack.Navigator initialRouteName="Jobs" screenOptions={{headerShown: false}}>
<Stack.Screen name="Jobs" component={Profile} />
<Stack.Screen name="JobDetails" component={JobDetails} />
</Stack.Navigator>
);
}
function App() {
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={props => <SideBar { ...props } />}>
<Drawer.Screen name="Home" component={AuthenticationStack} />
<Drawer.Screen name="Jobs" component={UserStack} />
</Drawer.Navigator>
</NavigationContainer>
);
}
export default App;
The above code works but have some problems like,
While loading the app, initially the drawer is visible and goes hidden when the app loaded.
I do not want to have drawers for Authentication screens, If I have 2 different navigation without splitting AuthenticationStack and UserStack then I am facing problem while navigating from Login to Profile and vice versa
UPDATE 1
export default() => {
const [logged, setUser] = React.useState(false);
return (
<NavigationContainer>
{
logged
?
<DrawerScreen initialParams={{ setUser }} />
:
<AuthStackScreen initialParams={{ setUser }} />
}
</NavigationContainer>
);
}
Now in login.js, I need to update setUser to true, right? If yes how can I access setUser in my login.js file
Update 2
class Login extends Component {
fetch(login_url, {
method: "POST",
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Accept-Language': 'en-US' },
body: JSON.stringify(data)
})
.then((response) => {
const success = response.ok;
const data = response.json();
return Promise.all([success, data]);
})
.then(([success, response]) => {
var data, userInfo;
if (success) {
data = {
token: response.token,
user: response.user,
}
if(_storeUserData(response)) {
_retrieveUserData().then((userInfo) => {
this.setState({
logged: true,
userInfo: userInfo,
loading:false,
email: '',
password: ''
});
navigate('Profile');
})
.catch((error) => {
// alert(error);
});
}
}
this.setState(state_data);
})
.catch((error) => {
alert('Error:'+error);
this.setState({ loading: false});
});
}
You can create something similar to the switch navigator by conditionally rendering navigators like below.
export default function App() {
const [loggedIn, setLoggedIn] = React.useState(false);
return (
<NavigationContainer>
{loggedIn ? (
<Drawer.Navigator initialRouteName="Home">
<Drawer.Screen name="Home" component={HomeScreen} initialParams={{ setLoggedIn }}/>
<Drawer.Screen name="Notifications" component={NotificationsScreen} />
</Drawer.Navigator>
) : (
<Stack.Navigator>
<Stack.Screen
name="Auth"
component={AuthScreen}
initialParams={{ setLoggedIn }}
/>
</Stack.Navigator>
)}
</NavigationContainer>
);
}
You can tryout this snack for a working example
https://snack.expo.io/#guruparan/switchsample

Navigating between different stackscreen in react-navigation 5

isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</>
) : (
<>
<Stack.Screen name="SignIn" component={SignInScreen} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)
How can I navigate from signInScreen to HomeScreen.
You can make a context to handle the authentification flow and on your app launch check if a token or anything is already set.
const Index = () => {
const [authState, dispatch] = useAuthContext();
useEffect(() => {
const boostrapAsync = async () => {
let userToken = null;
try {
userToken = await AsyncStorage.getItem('userToken');
} catch (e) {
console.log('Restoring token failed');
}
restoreToken({ token: userToken }, dispatch);
};
boostrapAsync();
}, []);
if (authState.isLoading) {
return (
<SplashScreen />
);
}
return (
<Root>
<NavigationContainer>
<UIProvider reducer={UIReducer} initialState={initialStateUIReducer}>
{authState.userToken ? (
<UserProvider reducer={UserReducer} initialState={initialStateUserReducer}>
<AppNavigation />
</UserProvider>
) : <AuthNavigation />}
</UIProvider>
</NavigationContainer>
</Root>
);
};
export default Index;
These problems can be solved with a splash screen. I think you should collect all screens under one tag. Then, make a splash screen. After that, you can control the parameter is called isSignedIn in the SplashScreen. Then you navigate the screen which you want.