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.
Related
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.
In my React-native project in the HomeScreen, I get some values from AsyncStorage. After getting this value I compare it and take decision in which screen it will go next.
If the getValue is null then it will go the WelcomeScreen and if it is not null then it will go the HomeDrawer Screen.
Here I have provided the code-
import React from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import {StackNavigator} from 'react-navigation';
import WelcomeScreen from './WelcomeScreen';
import LoginScreen from './components/LoginScreen';
import NoteMeHome from './components/NoteMeHome';
import HomeDrawer from './HomeDrawer/HomeDrawer';
import SettingsScreen from './components/SettingsScreen';
class HomeScreen extends React.Component {
state = {
getValue: '',
}
async componentDidMount() {
const token = await AsyncStorage.getItem('toke');
this.setState({ getValue: token });
}
render() {
console.log('#ZZZ:', this.state.getValue);
if(this.state.getValue !== null) {
return (
<AppStackNavigator/>
);
} else {
return (
<AppStackNavigator2/>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStackNavigator = new StackNavigator({
HomeDrawer: {screen:HomeDrawer},
WelcomeScreen: {screen:WelcomeScreen},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
})
const AppStackNavigator2 = new StackNavigator({
WelcomeScreen: {screen:WelcomeScreen},
HomeDrawer: {screen:HomeDrawer},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
})
export default HomeScreen;
Now, after running this, if I get null value in the variable getValue , then it is showing the following warning-
Warning: Can't call setState(or forceUpdate) on an unmounted
component. this is a no-op, but it indicates a memory leak in your
application. To fix, cancel all subscription and asynchronous tasks in
the componentWillUnmount method.
So, how can I solve this warning issue?
I don't know whether it's a good practice or not. The problem was- my component was initializing with empty string and I was checking for null in render function. Initializing getvalue with null or checking for empty string in render would solve this issue.
So, the change I made in my code is -
state = {
getValue: ''
}
And it removes the warning.
A better solution would be to use the SwitchNavigator from react-navigation since your navigation stacks are identical and you only want to route to the first screen based on that token.
see example
import React from 'react';
import { StyleSheet, Text, View, AsyncStorage } from 'react-native';
import {StackNavigator, createSwitchNavigator} from 'react-navigation';
import WelcomeScreen from './WelcomeScreen';
import LoginScreen from './components/LoginScreen';
import NoteMeHome from './components/NoteMeHome';
import HomeDrawer from './HomeDrawer/HomeDrawer';
import SettingsScreen from './components/SettingsScreen';
const AppStackNavigator = new StackNavigator({
HomeDrawer: {screen:HomeDrawer},
LoginScreen: {screen:LoginScreen},
NoteMeHome: {screen:NoteMeHome},
SettingsScreen: {screen:SettingsScreen}
});
export default createAppContainer(createSwitchNavigator(
{
LaunchScreen,
WelcomeScreen,
AppStackNavigator,
},
{
initialRouteName: 'LaunchScreen',
}
));
class LaunchScreen extends React.Component {
constructor(props) {
super(props);
this._getToken();
}
// Fetch the token from storage then navigate to the appropriate place
_getToken = async () => {
const tok = await AsyncStorage.getItem('toke');
// This will switch to the Welcome screen or main AppStack. Then this launch
// screen will be unmounted and thrown away.
this.props.navigation.navigate(tok ? 'AppStackNavigator' : 'WelcomeScreen');
};
// Render any loading content that you like here
render() {
return (
<View>
{/*...*/}
</View>
);
}
}
I am developing an application and using the listener onAuthStateChanged from Firebase in my AuthenticationLoadingScreen so I can redirect the user based on this state, however, it just opens a white page and doesn't open the home screen.
EDIT: Someone says that maybe the app.js is the problem so I added it here
import HomeScreen from '../screens/HomeScreen'
import AuthScreen from '../screens/AuthScreen'
// Implementation of HomeScreen, OtherScreen, SignInScreen, AuthLoadingScreen
// goes here.
const AppStack = createStackNavigator({ Home: HomeScreen});
const AuthStack = createStackNavigator({ Home: HomeScreen });
export default createAppContainer(createSwitchNavigator(
{
AuthLoading: AuthScreen,
App: AppStack,
Auth: AuthStack
},
{
initialRouteName: 'AuthLoading',
}
));
export default class AuthScreen extends React.Component {
constructor(props){
super(props);
}
componentDidMount = ()=>{
firebase.auth().onAuthStateChanged(user => {
this.props.navigation.navigate(user ? 'App' : 'Auth');
});
}
render() {
return (
<ImageBackground
source={require('../assets/images/AuthScreenBackground.png')}
style={styles.ImageBackground}/>
);
}
}
Here is the App.js code:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import AppNavigation from './navigation/AppNavigation.js'
import * as firebase from 'firebase'
var config = {
};
firebase.initializeApp(config);
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<AppNavigation/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
Try this code:
componentWillMount() {
this.observeLogin = firebase.auth().onAuthStateChanged(user => {
if (user) {
this.props.navigation.navigate('App');
} else {
this.props.navigation.navigate('Auth');
}
});
}
componentWillUnmount() {
// Don't forget to unsubscribe when the component unmounts
this.observeLogin ();
}
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