So I built a login system with React Native, React Navigation and AsyncStorage. If the user clicks on a button he gets logged in and the value of the AsyncStorage Key "@loginuser" gets refreshed. Now I expected that the screen automatically gets refreshed, but I have to close the App and start it again - This is not optimal.
(I also saw React-Native/React navigation redirection after login with redux post, but it is very old)
App.js
import React from 'react';
import { Text, View, StyleSheet, Button, TouchableOpacity, TextInput } from 'react-native';
import DefaultStackNavigation from './components/Navigation/Navigation';
const App = () => {
return(
<View>
<DefaultStackNavigation />
</View>
)
}
export default App;
Navigation.js
import React, {useEffect, useState} from 'react';
//React Native
import { Text, View, StyleSheet} from 'react-native';
//Screens
import HomeScreen from '../HomeScreen/HomeScreen'
import AddScreen from "../AddScreen/AddScreen";
import NotificationScreen from "../NotificationScreen/NotificationScreen";
import MenuScreen from "../MenuScreen/MenuScreen";
import SearchScreen from "../SearchScreen/SearchScreen";
import PostJobScreen from "../PostJobScreen/PostJobScreen";
import JobOfferScreen from "../JobOfferScreen/JobOfferScreen";
import ProfileScreen from "../ProfileScreen/ProfileScreen";
import NoneLoggedinScreen from "../NoneLoggedinScreen/NoneLoggedinScreen"
import SignupModal from "../NoneLoggedinScreen/SignupModal"
//React Navigation
import { createStackNavigator } from "@react-navigation/stack";
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
//Third Party
import AsyncStorage from '@react-native-async-storage/async-storage';
const Tab = createBottomTabNavigator();
//Tab bar
const HomeTabs = () => {
return (
<Tab.Navigator tabBarOptions={{style:{height: 50}}, {showIcon: true}, {showLabel: false}} >
<Tab.Screen name="Home" component={HomeScreen}/>
<Tab.Screen name="SearchScreen" component={SearchJobStack}/>
<Tab.Screen name="AddScreen" component={AddScreen}/>
<Tab.Screen name="NotificationScreen" component={NotificationScreen} />}}/>
<Tab.Screen name="MenuScreen" component={MenuScreen}/>}}/>
</Tab.Navigator>
);
}
const Stack = createStackNavigator();
const STORAGE_KEY = '@loginStatus'
const DefaultStackNavigation = () => {
const [loginStatus, setLoginStatus] = useState()
const readData = async () => {
try {
const isLoggedIn = JSON.parse(await AsyncStorage.getItem(STORAGE_KEY))
console.log(isLoggedIn)
if (isLoggedIn !== null) {
setLoginStatus(isLoggedIn)
}
} catch (e) {
alert('Failed to fetch the data from storage')
}
}
readData()
return loginStatus ? (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
<Stack.Screen name="HomeTabs" component={HomeTabs} />
<Stack.Screen name="PostJobScreen" component={PostJobScreen} />
<Stack.Screen name="ProfileScreen" component={ProfileScreen}/>
</Stack.Navigator>
</NavigationContainer>
) : (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}} independent={false}>
<Stack.Screen name="NoneLoggedinScreen" component={NoneLoggedinScreen} />
<Stack.Screen name="SignupModal" component={SignupModal} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default DefaultStackNavigation;
NoneLoggedinScreen.js
import React, { useState, useEffect } from 'react';
import { Text, View, Button} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = '@loginStatus'
const SignupModal = () => {
const [loginStatus, setLoginStatus] = useState(false)
const saveData = async (parmLoginStatus) => {
try {
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(parmLoginStatus))
alert('Data successfully saved -> Logged In')
console.log(loginStatus)
} catch (e) {
alert('Failed to save the data to the storage')
}
}
const onSubmitLogin = () => {
setLoginStatus(true)
saveData(true)
}
return(
<View>
<Text>Login Page. Press the button to log in and stay logged in</Text>
<Button title="Log in" onPress={() => onSubmitLogin()}/>
</View>
);
};
const styles = StyleSheet.create({
App: {
flex: 1,
backgroundColor: "white"
},
});
export default SignupModal;
Now I was expecting the page to reload and I can use the app. Unfortunately it doesn't. The data is saved and the login status is set to true
, but I have to restart the app to use it. The perfidious thing is, if I write the AsyncStorage login logic from the NoneLoggedinScreen.js
file into the App.js file, the app works fine -> However, this is not an alternative for me, because the general structure of the app is, I think, built relatively sensibly.
Also when the user tries to be redirected manually (by button) with navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist, which is also not understandable, because the navigation has actually been set to Logged in now. Has anyone ever had this problem?
This are my dependencies by the way
"dependencies": {<br>
"@react-native-async-storage/async-storage": "^1.15.1",<br>
"@react-native-community/masked-view": "^0.1.10",<br>
"@react-navigation/bottom-tabs": "^5.11.7",<br>
"@react-navigation/native": "^5.9.2",<br>
"@react-navigation/stack": "^5.14.2",<br>
"react": "16.13.1",<br>
"react-native": "0.63.4",<br>
"react-native-gesture-handler": "^1.10.1",<br>
"react-native-reanimated": "^1.13.2",<br>
"react-native-safe-area-context": "^3.1.9",<br>
"react-native-screens": "^2.17.1",<br>
}
In your authentication strategy, there is a hidden assumption that when you save data using AsyncStorage in NoneLoggedInScreen
, the data will immediately be read using the readData
function in DefaultStackNavigation
. The reason why this does not happen is that readData
is only called when DefaultStackNavigation
renders/re-renders. This will not happen when one of its children sets some data in local storage.
There are many ways to fix this. Using redux with redux-persist or another state-management and persistence setup, or a purpose-built context-based solution (see eg. this Kent C. Dodds article on Authentication in React Applications).
The second, minor issue that you mention:
navigation.navigate("HomeTabs") after logging in doesn't work, and I get an error that Home doesn't exist
does make sense, and the reason behind it is that you conditionally render two different NavigationContainers
. They don't know about each others routes, so you can't navigate between them.
To fix this, you should render one NavigationContainer
and conditionally render one of the Stack.Navigator
s as its child.