I have a problem with React Native Navigation Library, The problem is that I implemented logout in my application that forget the AsyncStorage presistent data, the problem starts when I log to another account without restarting the application, I find that the navigation drawer does not sense the change in AsyncStorage although I update the state depending on returned value from AsyncStorage as if it is cached, so I want a method to refresh the navigation draw or flush the navigation drawer cached view once I made a logout out and re-logged to another account to change the name and thumbnail content.
I tried to find any event listener or any callback where I can replace with my own logic but I didn't find anything closely related.
I also tried to replace AsyncStorage with axios call directly to fetch the user but nothing worked.
My Navigator Code:
import {createStackNavigator, createAppContainer, createDrawerNavigator} from 'react-navigation';
import {
Login,
Register,
ShowProperty,
AllProps,
Splash,
Search,
SearchResult,
EditProfile,
AddProperty,
Home,
HomeSearch,
Contact,
About,
} from "./pages";
import {NavDrawer} from "./components";
import {Dimensions} from 'react-native';
const StackNavigator = createStackNavigator(
{
Welcome: {
screen: Splash
},
Register: {
screen: Register
},
Login: {
screen: Login
},
ShowProps: {
screen: AllProps
},
ShowProp: {
screen: ShowProperty
},
Search: {
screen: Search
},
SearchResult: {
screen: SearchResult
},
EditProfile: {
screen: EditProfile
},
AddProperty: {
screen: AddProperty
},
Home: {
screen: Home,
},
HomeSearch: {
screen: HomeSearch,
},
About: {
screen: About,
},
Contact: {
screen: Contact,
},
},
{
initialRouteName: "Welcome",
headerMode: "none",
duration: 500,
lazy: true,
}
);
const DrawerNavigation = createDrawerNavigator({
Drawer: {
screen: NavDrawer,
},
Application: StackNavigator,
}, {
initialRouteName: "Application",
headerMode: "none",
duration: 500,
lazy: true,
contentComponent: NavDrawer,
drawerWidth: Dimensions.get('window').width,
drawerPosition: 'right',
});
export default createAppContainer(DrawerNavigation);
and this is my custom drawer code:
import React, {Component} from 'react';
import {ActivityIndicator, Image, ImageBackground, StyleSheet, View, TouchableOpacity} from 'react-native';
import Responsive from "./Responsive";
import AsyncStorage from '#react-native-community/async-storage';
import {FontText} from "./index";
import Icon from 'react-native-vector-icons/EvilIcons';
import axios from 'axios';
import {NavigationActions, StackActions} from "react-navigation";
class NavDrawer extends Component {
state = {
profile_picture: null,
name: null,
};
componentDidMount = async () => {
const user = await AsyncStorage.getItem('#UserData:user');
if(user !== null){
let {first_name, last_name, profile_picture} = JSON.parse(user).data;
console.log(first_name, last_name, profile_picture);
let stateObj = {
profile_picture: profile_picture ? profile_picture : null,
name: first_name && last_name ? first_name +" "+ last_name : null,
};
this.setState({
...stateObj
});
}
};
handleNavigation = (routeName) => (e) => {
this.props.navigation.closeDrawer();
this.props.navigation.navigate(routeName);
};
resetAndNavigate = (route) => {
let navigator = StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName: route,
}),
],
});
this.props.navigation.dispatch(navigator);
};
clearCredentials = async (e) => {
try{
await AsyncStorage.clear();
this.resetAndNavigate("Login");
}
catch (e) {
this.resetAndNavigate("Login");
}
};
render() {
return (
<ImageBackground source={require('../images/drawer-image.jpg')} style={style.backgroundStyle}>
<View style={style.infoWrapper}>
<Image source={this.state.profile_picture ?
{uri: this.state.profile_picture} :
require('../images/man.jpg')
} style={style.img}
/>
<FontText wFont={'bold'} style={style.nameStyle}>
{this.state.name ? this.state.name : "Loading.."}
</FontText>
</View>
<View style={style.navigators}>
<TouchableOpacity onPress={this.handleNavigation('Home')}>
<FontText style={style.nameStyle}>
Home
</FontText>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleNavigation('About')}>
<FontText style={style.nameStyle}>
About us
</FontText>
</TouchableOpacity>
<TouchableOpacity onPress={this.handleNavigation('Contact')}>
<FontText style={style.nameStyle}>
Contact us
</FontText>
</TouchableOpacity>
<TouchableOpacity onPress={this.clearCredentials}>
<FontText style={style.nameStyle}>
Logout
</FontText>
</TouchableOpacity>
<TouchableOpacity style={style.dismiss} onPress={this.props.navigation.closeDrawer}>
<Icon name={"close"} color={"#fff"} size={35}/>
</TouchableOpacity>
</View>
</ImageBackground>
);
}
}
export default NavDrawer;
The expected is when I logged out and re-login with another account is to see the name and photo of current user in drawer.
The actual behavior is that navigation drawer caches the custom component for the drawer and when I log with another user I see the information of the previous user.
Related
Hi I am implementing react navigation in a react native app, and I am following the docs on react navigation. And I when I run the code this is the result:
My question is how do I make the center content's width same as the screen.
Also, his is my first time using react native expo after switching from reactJS
Code:
navigator code:
import Login from "./Login";
import Signup from "./Signup";
import {
createAppContainer,
NavigationContainer,
NavigationNavigator,
} from "react-navigation";
import { createStackNavigator } from "react-navigation-stack";
import Chat from "./Chat";
import Error from "./Error";
/**
* This is the screen stack of the navigation stack.
*/
const screens: any = {
default: { screen: Login },
signup: { screen: Signup },
chat: { screen: Chat },
Error: { screen: Error },
};
const stack: NavigationNavigator<any, any> = createStackNavigator(screens);
const container: NavigationContainer = createAppContainer(stack);
export default container;
App.tsx:
import { StatusBar } from "expo-status-bar";
import React from "react";
import { Alert, StyleSheet, Text, View } from "react-native";
import * as expoAppLoading from "expo-app-loading";
import loadFonts from "./assets/fonts/loader";
import Navigator from "./screens/navigator";
/**
* This is the main app component of the Chill&chat application.
*/
const App: React.FC = () => {
const [loading, setLoading] = React.useState(true);
const style: any = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
text: {
fontFamily: "poppinsBold",
},
});
if (loading) {
return (
<expoAppLoading.default
startAsync={async (): Promise<void> => {
await loadFonts();
}}
onFinish={(): void => {
setLoading(false);
}}
onError={(): void => {
Alert.alert("Error", "Error loading fonts");
}}
/>
);
} else {
return (
<View style={style.container}>
<Navigator />
<StatusBar style="auto" />
</View>
);
}
};
export default App;
You should be able to set the width by adding percentage dimensions to your style sheet for the desired element. This may require you do away with flex layout however, or use flex in a parent instead.
container: {
width:'100%',
},
This should solve for the width problem though.
I am trying different ways to display a conditional button based on the athtentication state, but i keep getting into trouble. I have an app.js that defines the stacknavigator, which adds a button to the header giving the option to log out if authenticated.
I wrote a handleLogout function that should perform this.
import React from 'react';
import { Button, Image, View, Text } from 'react-native';
import firebase from 'react-native-firebase';
import Loading from './Loading';
import SignUp from './SignUp';
import Login from './Login';
import Main from './Main';
import {createAppContainer} from 'react-navigation';
import {createStackNavigator} from 'react-navigation-stack';
import { useNavigation } from '#react-navigation/native';
// eslint-disable-next-line no-undef
handleLogOut = () => {
const navigation = useNavigation();
firebase
.auth()
.signOut()
.then(() => this.props.navigation.navigate('Login'))
.catch(error => this.setState({errorMessage: error.message}));
};
const AppNavigator = createStackNavigator(
{
Loading: Loading,
SignUp: SignUp,
Login: Login,
Main: Main,
},
{
initialRouteName: 'Loading',
defaultNavigationOptions: {
headerLeft: null,
headerRight: () => {
let button = this.loggedIn? (
<Button
onPress={this.handleLogOut}
title="Log-out"
color="#fff"
/>
)
:
(
<Button
onPress={() => alert('Please log in')}
title="Log-in"
color="#fff"
/>
)
return button;
},
headerStyle: {
backgroundColor: '#c6f1e7',
},
headerTintColor: '#59616e',
headerTitleStyle: {
fontFamily: 'Raleway-Regular',
fontWeight: '400',
},
},
},
);
export default createAppContainer(AppNavigator);
App.js calls on loading.js where the value for loggedin is declared, based on the authentciation state and then loads either main.js or sign-up. in this case the main page is loaded, which means that someone is authenticated:
// Loading.js
import React from 'react';
import {View, ActivityIndicator, StyleSheet} from 'react-native';
import firebase from 'react-native-firebase';
export default class Loading extends React.Component {
componentDidMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.setState({ loggedIn: true })
this.props.navigation.navigate(user ? 'Main' : 'SignUp');
} else {
this.setState({ loggedIn: false })
}
});
}
render() {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#c6f1e7',
},
});
Now the page redirects to main and shows the welcome message, which indicates that the user is logged in, but the button in the header is saying 'log-in' as well, which means the button is not chosen well. I assume that this is because the loggedin value is not read and it automatically sets it on loggedin: false.
Here is the code for main.js
// Main.js
import React from 'react';
import { View, Text, StyleSheet, Button } from 'react-native';
import firebase from 'react-native-firebase';
import { createAppContainer } from 'react-navigation';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import Kowops from './Kowops';
import Scan from './Scan';
import Wallet from './Wallet';
export class Main extends React.Component {
state = { currentUser: null }
componentDidMount() {
const { currentUser } = firebase.auth()
this.setState({ currentUser })
}
render() {
const { currentUser } = this.state
return (
<View style={styles.container}>
<Text>
Hidiho {currentUser && currentUser.email}!
</Text>
</View>
)
}
}
const bottomTabNavigator = createBottomTabNavigator(
{
Main: {screen: Main},
Kowops: {screen:Kowops},
Scan: {screen:Scan},
Wallet: {screen:Wallet},
},
{
//initialRouteName: 'Main',
tabBarOptions: {
initialRouteName: 'Main',
activeTintColor: '#59616e',
inactiveTintColor: '#a9a9a9',
style: {
backgroundColor: '#c6f1e7',
}
},
});
export default createAppContainer(bottomTabNavigator);
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
})
So I need to figure out how to ensure that the value of isloggedin is read properly and the script loads the right button.
Does anyone have a clue?
Thanks in advance!!
Tim
The key here is that you can't use state across different components without passing them as props or through navigation params in this case. You can't use the useNavigation hook outside of a functional component so you should pass the navigation object around when you need it outside of a component (handleLogout is not a component).
Here are some alterations I would make, however I would suggest that you will need to make further changes based on the idea that you can use navigation params to pass information between screens. See more here https://reactnavigation.org/docs/en/params.html.
App.js
DefaultNavigationOptions can be a function which has a navigation prop, this is the navigation object you can use to get params or navigate in the context of the router.
remove that eslint exception because you don't need it, just properly declare the variable. Remove the "this" from you handleLogout function call because it is not a class attribute. Use navigation.getParam to get the isLoggedIn variable which you can pass in the navigate function call.
const handleLogout = navigation => {
firebase
.auth()
.signOut()
.then(() => navigation.navigate('Login'))
.catch(error => this.setState({errorMessage: error.message}));
}
...
defaultNavigationOptions: ({navigation}) => ({
headerRight: () => {
const isLoggedIn = navigation.getParam('isLoggedIn', false);
let button = isLoggedIn ? (
<Button
onPress={() => handleLogOut(navigation)}
title="Log-out"
color="#fff"
/>
) : ...
} ...
Now Loading.js
Here you need to add a navigation param to your navigate call which can then be used in the header
...
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.navigation.navigate('Main', {isLoggedIn: true});
} else {
this.props.navigation.navigate('SignUp', {isLoggedIn: false});
}
});
here is a modified version of your code in snack that you can see will get the logged in param and show the logout button https://snack.expo.io/#dannyhw/intrigued-graham-crackers
You will need to make further changes to fix other functionality because I only changed the minimum to show that you can use the navigation param.
My application contain drawer navigator and stack navigator. But issue is that when i tried to goback it takes me to first screen even if i am 2-3 stack deep. It don't show previous screen it always takes me to main screen. Below is my App.js code
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
Button,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import {StackNavigator, SwitchNavigator, DrawerNavigator} from 'react-navigation';
import Screen1 from './Screen/Screen1';
import Screen2 from './Screen/Screen2';
import Screen3 from './Screen/Screen3';
import Screen4 from './Screen/Screen4';
import Screen5 from './Screen/Screen5';
import ScreenList from './Screen/ScreenList';
import Login from './Screen/Login';
class AuthLoadingScreen extends React.Component {
constructor() {
super();
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('user');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
this.props.navigation.navigate(userToken ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
return (
<View style={styles.container}>
<ActivityIndicator/>
<StatusBar barStyle="default"/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStack = DrawerNavigator({
Screen1: { screen: Screen1},
Screen2: { screen: Screen2},
Screen3: { screen: Screen3},
Screen4: { screen: Screen4},
Screen5: { screen: Screen5},
ScreenList: { screen: ScreenList},
}, {contentComponent: SideBar});
const AuthStack = StackNavigator({Login: { screen: Login}},{headerMode:'none'});
const MyNavigator = SwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading'
}
);
export default class App extends React.Component {
render() {
return <MyNavigator />;
}
}
From Screen 1, i click on button and go to screen 2 and screen 3 like:
onPress={() => navigate('Screen2', { })}
And it works fine, but when i go back using bellow code from screen 3 it takes me to screen 1 not screen 2
this.props.navigation.goBack();
Am i missing something?
Did you try
1 -> 2 -> 3 -> 4
<Button
onPress={() => goBack('3')}
title="Go to 3 screen"
/>
I've been working on my first React Native project. It is a Multiscreen App, and one of the screens requires some data, which I'm passing from the parent screen. But the issue is that when I receive the data in the child screen, and try to save the value in the screen's state, the app crashes. As soon as I remove the code that accesses the state, it does not crash. Here's the code:
renderRow(object) { //Calling the second screen when the user touches
const { navigate } = this.props.navigation;
return(
<TouchableOpacity onPress={() => navigate('ViewRecord', { key: object.key })}> //Sending the parameter
<Image
styleName="large-square"
source={{ uri: object.record.image }} >
<Text style={styles.designNumberHeading}>{object.record.designNumber}</Text>
</Image>
</TouchableOpacity>
);
}
Here is the code of the Child Screen:
'use-strict'
import React, { Component } from 'react';
import {
StyleSheet,
View,
KeyboardAvoidingView,
ScrollView,
TouchableHighlight,
Text,
FlatList,
} from 'react-native';
import {
Heading,
Title,
TextInput,
Button,
Image,
NavigationBar,
ListView,
TouchableOpacity,
Icon,
Lightbox,
Overlay,
} from '#shoutem/ui';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import NavBar from '../components/NavBar.js';
import { firebaseApp } from '../config/firebase.js';
export default class ViewRecord extends Component{
constructor(props) {
super(props);
this.state={
designNumber: '',
dates: [],
quantity: 0,
karigars: [],
colour:'',
amount: '',
description: '',
editMode: false,
};
this.updateStateAfterGettingData = this.updateStateAfterGettingData.bind(this);
}
getData() {
const { params } = this.props.navigation.state; //Accessing the parameters
console.log('Key: ', params.key);
let databaseRef = firebaseApp.database().ref(params.key);
databaseRef.once('value', (snap) => {
console.log('Inside Event Listener');
this.updateStateAfterGettingData(snap); //Setting the state. (The App crashes if this statement is executed)
});
}
updateStateAfterGettingData(snap) {
this.setState({
designNumber: snap.val().designNumber,
dates: snap.val().dates,
quantity: snap.val().quantity,
karigars: snap.val().karigars,
colour: snap.val().colour,
amount: snap.val().amount,
materials: snap.val().materials,
description: snap.val().description,
});
console.log(this.state);
}
render() {
const { goBack } = this.props.navigation;
this.getData();
if (!this.state.editMode) {
return(
<View style={styles.container}>
<NavBar title="View Record" hasHistory={true} goBack={() => goBack()}/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
})
Here is the code of the StackNavigator:
import { StackNavigator } from 'react-navigation';
import CreateRecord from '../screens/CreateRecord.js';
import Records from '../screens/Records.js';
import ViewRecord from '../screens/ViewRecord.js';
export const Navigator = StackNavigator({
Records: { screen: Records },
CreateRecord: { screen: CreateRecord },
ViewRecord: { screen: ViewRecord },
},
{
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
});
Now, how can I manipulate the state of the ViewRecord.js class when accepting parameters?
PS: I am using Expo along with Create React Native App
I have a tab navigator where MyHomeScreen is the initial view.
In my Homescreen i have another component with an action to navigate to another screen but when i press it is not navigating to AnotherscreenWithouttab.
MyHomeScreen
render() {
const navigation = this.props.navigation;
const {navigate} = this.props.navigation;
return (
<Actionscreen
navigation={navigation}
/>
</View>
);
}
ActionScreen
render() {
const {navigate} = this.props.navigation;
return (
<View >
<TouchableOpacity
onPress={ () => navigate('AnotherscreenWithouttabRoute', { param1: 'Route-1' }) }
><Text>go to another screen</Text></TouchableOpacity>
</View>
);
}
TabNavigator
const DashboardTab = TabNavigator(
{
Home: {
screen: MyHomeScreen,
path: '',
},
People: {
screen: FeedScreen,
path: 'cart',
}
}
class DashboardTabComponent extends Component {
constructor(props) {
super(props);
}
render() {
const {
navigation,
screenProps
} = this.props;
return(
<DashboardTab
//navigation={navigation}
screenProps={screenProps}
//screenProps={this.state}
/>
);
}
}
router.js
import React, { Component } from 'react';
import { Text, View, ScrollView, TouchableOpacity, AsyncStorage, ActivityIndicator, Image, StyleSheet } from 'react-native';
import { DrawerNavigator, DrawerItems, DrawerView, StackNavigator, TabNavigator, Header } from 'react-navigation';
import { HomeComponent } from "./Home/HomeComponent";
import { AboutusComponent } from "./Aboutus/AboutusComponent";
import { AnotherscreenWithouttabComponent } from "./AnotherscreenWithouttab";
import FeedAndSearchTab from "./feed/FeedAndSearchTab";
import { DetailsScreen1 } from "./Details/DetailsScreen1";
import { DetailsScreen2 } from "./Details/DetailsScreen2";
import * as css from "./Styles/Styles";
import { Button, Icon } from "react-native-elements";
//
// tabs
//
const nav_tab = TabNavigator(
// route config
{
DetailsRoute1: { screen: DetailsScreen1 },
DetailsRoute2: { screen: DetailsScreen2 },
},
// navigator config
{
lazy: true, // render the tabs lazily
tabBarPosition: 'bottom', // where are the tabs shown
backBehavior: 'none', // back button doesn't take you to the initial tab
tabBarOptions: css.tabs
},
);
//
// stack
//
export const AppNavigator = (isIntroRead = false, isSignedIn = false) => {
return StackNavigator(
// route config
{
HomeRoute: { screen: HomeComponent, path: 'home', }, // this is displayed first
AboutusRoute: { screen: AboutusComponent },
AnotherscreenWithouttabRoute: { screen: AnotherscreenWithouttabComponent , path: 'appointment/schedule', },
},
// navigator config
{
//headerMode: 'none', // this removes the navigation header
initialRouteName: isIntroRead ? 'HomeRoute' : 'IntroRoute', // HomeRoute /IntroRoute AppointmentScheduleRoute
navigationOptions: ({ navigation, screenProps }) => {
return {
headerTitle: <Title
navigation={navigation}
titleName={'Doctor On Call'}
/>, // label text // headerTitle: "Hi",
...css.header, // other styling
headerLeft: <TitleAndIcon navigation={navigation} />,
headerRight: <MenuIcon
navigation={navigation}
screenProps={screenProps}
/>,
header: (props) => <ImageHeader {...props} />,
}
},
cardStyle:{backgroundColor: '#FFFFFF', shadowOpacity: 0, shadowColor: 'transparent', }
}
);
};
//export default AppNavigator;