React native: reset route, back to home page - react-native

My idea is to have 2 navigators:
StackNavigator - Home page, login page, sign up page
DrawerNavigator - pages for logged in users
Now, here's my code:
// App.js
import React, { Component } from 'react'
import { MainNavigator } from './src/components/main/MainNavigator'
import { UserNavigator } from './src/components/user/UserNavigator'
import { createSwitchNavigator, createAppContainer } from 'react-navigation'
export default class App extends Component {
constructor(props) {
super(props)
}
render() {
const Navigator = createAppContainer(
makeRootNavigator(this.state.accessToken)
)
return <Navigator />
}
}
const makeRootNavigator = (isLoggedIn) => {
return createSwitchNavigator(
{
Main: {
screen: MainNavigator
},
User: {
screen: UserNavigator
}
},
{
initialRouteName: isLoggedIn ? "User" : "Main"
}
)
}
Next, my MainNavigator.js:
import { createStackNavigator } from 'react-navigation'
import MainPage from './MainPage'
import LoginPage from './LoginPage'
export const MainNavigator = createStackNavigator({
Main: {
screen: MainPage
},
Login: {
screen: LoginPage
},
},
{
headerMode: 'none',
navigationOptions: {
headerVisible: false,
}
},
{
initialRouteName: "Main"
})
Login page has following relevant code:
export default class LogInPage extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<View style={styles.container}>
<LoginButton
onLoginFinished={
(error, result) => {
if (error) {
console.log("login has error: " + result.error);
} else if (result.isCancelled) {
console.log("login is cancelled.");
} else {
AccessToken.getCurrentAccessToken().then(
(data) => {
console.log(data.accessToken.toString())
this.props.navigation.navigate('User')
}
)
}
}
}
onLogoutFinished={() => console.log("logout.")}
/>
</View>
)
}
}
Now, that works like a charm. It takes me to my UserNavigator, which looks like this:
import { createDrawerNavigator } from 'react-navigation'
import ProfilePage from './ProfilePage'
import {MainNavigator} from '../main/MainNavigator'
export const UserNavigator = createDrawerNavigator({
Profile: {
screen: ProfilePage,
},
MainNavigator: {
screen: MainNavigator
}
},
{
initialRouteName: "Profile"
})
So, once I'm logged in, profile page properly shows up. Now, the problem is following: when I use logout button, it runs onLogoutFinished(), which is where I want to change navigation back to the main page, but can't seem to do it, whichever way I try. I tried both:
onLogoutFinished={() => {
console.log("logout.")
this.props.navigation.dispatch(NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'MainNavigator' })
]
}));
and
onLogoutFinished={() => {
console.log("logout.")
this.props.navigation.navigate('MainNavigator'));
}}
but both produce: Undefined is not an object ( evaluating '_this2.props.navigation.dispatch' ). Any ideas?

Related

How to use hook with SwitchNavigator

I'm trying to use https://github.com/expo/react-native-action-sheet with switch navigator.
I'm not sure how to do the basic setup as the example in the readme is different than my App.js. I'm using react 16.8 so I should be able to use hooks.
My App.js
import { useActionSheet } from '#expo/react-native-action-sheet'
const AuthStack = createStackNavigator(
{ Signup: SignupScreen, Login: LoginScreen }
);
const navigator = createBottomTabNavigator(
{
Feed: {
screen: FeedScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
Profile: {
screen: ProfileScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
},
);
const stackNavigator = createStackNavigator(
{
Main: {
screen: navigator,
// Set the title for our app when the tab bar screen is present
navigationOptions: { title: 'Test' },
},
// This screen will not have a tab bar
NewPost: NewPostScreen,
},
{
cardStyle: { backgroundColor: 'white' },
},
);
export default createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: stackNavigator,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
const { showActionSheetWithOptions } = useActionSheet();
);
Update, I'm getting this error when calling the showActionSheetWithOptions inside my component:
Hooks can only be called inside the body of a function component. invalid hook call
This is my code:
import React, { Component } from 'react';
import { useActionSheet } from '#expo/react-native-action-sheet'
export default class NewPostScreen extends Component {
_onOpenActionSheet = () => {
const options = ['Delete', 'Save', 'Cancel'];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
const { showActionSheetWithOptions } = useActionSheet();
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
buttonIndex => {
console.log(buttonIndex);
},
);
};
render () {
return (
<View>
<Button title="Test" onPress={this._onOpenActionSheet} />
</View>
)
}
}
update 2
I also tried using a functional component, but the actionsheet does not open (console does print "pressed")
// ActionSheet.js
import React from 'react';
import { Text, TouchableOpacity } from 'react-native';
import { useActionSheet } from '#expo/react-native-action-sheet'
export default function ActionSheet () {
const { showActionSheetWithOptions } = useActionSheet();
const _onOpenActionSheet = () => {
console.log("pressed");
const options = ['Delete', 'Save', 'Cancel'];
const destructiveButtonIndex = 0;
const cancelButtonIndex = 2;
showActionSheetWithOptions(
{
options,
cancelButtonIndex,
destructiveButtonIndex,
},
(buttonIndex) => {
console.log(buttonIndex);
},
);
};
return (
<TouchableOpacity onPress={_onOpenActionSheet} style={{height: 100,}}>
<Text>Click here</Text>
</TouchableOpacity>
);
};
Problem
As you can see here. You are not connecting your application root component.
Solution
import connectActionSheet from #expo/react-native-action-sheet and connect your application root component to the action sheet.
Simply modify your App.js to reflect the following:
// ... Other imports
import { connectActionSheet } from '#expo/react-native-action-sheet'
const AuthStack = createStackNavigator({
Signup: SignupScreen,
Login: LoginScreen
});
const navigator = createBottomTabNavigator({
Feed: {
screen: FeedScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
Profile: {
screen: ProfileScreen,
navigationOptions: {
tabBarIcon: tabBarIcon('home'),
},
},
});
const stackNavigator = createStackNavigator({
Main: {
screen: navigator,
// Set the title for our app when the tab bar screen is present
navigationOptions: { title: 'Test' },
},
// This screen will not have a tab bar
NewPost: NewPostScreen,
}, {
cardStyle: { backgroundColor: 'white' },
});
const appContianer = createAppContainer(
createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
App: stackNavigator,
Auth: AuthStack,
}, {
initialRouteName: 'AuthLoading',
})
);
const ConnectApp = connectActionSheet(appContianer);
export default ConnectApp;
Now on any of your application screens (i.e. Feed, Profile, Main, etc.) you can access the action sheet as follows:
If Stateless Component
// ... Other imports
import { useActionSheet } from '#expo/react-native-action-sheet'
export default function Profile () {
const { showActionSheetWithOptions } = useActionSheet();
/* ... */
}
If Statefull Component
// ... Other imports
import React from 'react'
import { useActionSheet } from '#expo/react-native-action-sheet'
export default Profile extends React.Component {
const { showActionSheetWithOptions } = useActionSheet();
/* ... */
}
Note: You can also access the action sheet as stated below from the docs
App component can access the actionSheet method as this.props.showActionSheetWithOptions

React Native Navigation Root issue with multiple views

Hello All I am facing an issue.
I am trying to set different root in app.js file but it always open Home View. Can anyone tell me what is wrong with my code. Any help would be highly appreciable!
import React, { Component } from "react";
import { View, Text, AsyncStorage, AppRegistry } from "react-native";
import LoginContainer from "./Login/LoginContainer";
import { createStackNavigator, createAppContainer } from "react-navigation";
import Home from "./Dashboard/Home";
const RootStack = createStackNavigator(
{
login: { screen: LoginContainer },
Home: { screen: Home }
},
{
initialRouteName: "login"
}
);
const AppContainer = createAppContainer(RootStack);
class Demo extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
email: ""
};
}
componentDidMount() {
const email = AsyncStorage.getItem("email").then(email => {
this.setState({
isLoading: false,
email: email
});
});
}
render() {
if (this.state.isLoading) {
return (
<View>
<Text>Loading..</Text>
</View>
);
}
if (this.props.email !== "") {
return <Home />;
} else {
return <AppContainer />;
}
}
}
export default Demo;
AppRegistry.registerComponent("Demo", () => Demo);
Instead of
export default Demo;
Use this :
export default AppContainer;
or
export default createAppContainer(RootStack);

How to pass props i.e. screenProps with Authentication flow using react-navigation

I want to pass this.props to screen components from app.js, below is my code.
I tried doing same but when I pass it as screenProp it shows as undefined in screen component.
App.js
import { createAppContainer, createSwitchNavigator, createStackNavigator } from 'react-navigation';
import HomeScreen from './HomeScreen';
import MapScreen from './MapScreen';
import LoginScreen from './LoginScreen';
import RegisterDeviceScreen from './RegisterDeviceScreen';
import React, { Component } from 'react';
import {
AsyncStorage,
} from 'react-native';
class AuthLoadingScreen extends Component {
componentDidMount() {
this._bootstrapAsync();
}
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('sessionText');
if (!userToken) {
await AsyncStorage.clear();
}
await this.props.navigation.navigate(userToken ? 'App' : 'Auth');
};
render() {
return <RegisterDeviceScreen screenProps={this.props} />;
}
}
const AppStack = createStackNavigator(
{
HomeScreen: { screen: HomeScreen },
MapScreen: { screen: MapScreen },
LoginScreen: { screen: LoginScreen },
RegisterDeviceScreen: { screen: RegisterDeviceScreen },
},
{
navigationOptions: {
gesturesEnabled: false,
}
});
const AuthStack = createStackNavigator(
{
LoginScreen: { screen: LoginScreen },
RegisterDeviceScreen: { screen: RegisterDeviceScreen },
HomeScreen: { screen: HomeScreen },
MapScreen: { screen: MapScreen },
},
{
navigationOptions: {
gesturesEnabled: false,
}
});
const SwitchNavigator = createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
Auth: AuthStack,
App: AppStack,
},
{
initialRouteName: 'AuthLoading',
}
);
const AppContainer = createAppContainer(SwitchNavigator);
export default AppContainer;
When I try to log it in componentdidmount() inside homescreen component it shows screenProp as undefined.

Navigation using 'react-native-navigation'

I have created app using 'react-native-navigation' and first navigation working fine.
Navigation.startSingleScreenApp({
screen: {
screen: 'drawer.HomeScreen',
title: '',
navigatorStyle: {
navBarHidden: true
}
}
});
I got issue in navigation
import { Navigation } from 'react-native-navigation';
and tried to navigate using
Navigation.push({
component: {
name: 'drawer.DashboardScreen'
}
});
startMainTab.js
const startTabs = () => {
Promise.all([
Icon.getImageSource("ios-share-alt", 30),
Icon.getImageSource("ios-menu", 30)
]).then(sources => {
Navigation.startTabBasedApp({
tabs: [{
screen: 'drawer.AnalyticsScreen',
navigatorButtons: {
leftButtons: [{
icon: sources[1],
title: "Menu",
id: 'sideDrawerToggle'
}]
}
},
{
screen: 'drawer.DashboardScreen',
navigatorButtons: {
leftButtons: [{
icon: sources[1],
title: "Menu",
id: 'sideDrawerToggle'
}]
}
}
],
drawer: {
left: {
screen: 'drawer.SideDrawer'
}
}
});
});
}
SideDrawer.js
export default startTabs;
export default class SideDrawer extends Component {
constructor(props) {
super(props);
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
}
componentWillMount() {
this.props.navigator.setOnNavigatorEvent(this.onNavigationEvent)
}
onNavigationEvent = (event) => {
// handle a deep link
if (event.type == 'DeepLink') {
const parts = event.link;
alert("Scree: " + parts)
//this.navigateToAbout()
this.props.navigator.push({
screen: "drawer.HomeScreen"
})
if (parts == 'drawer.DashboardScreen') {
//this.onPressScreen1();
}
}
}
navigateToAbout = () => {
this.props.navigator.push({
screen: 'drawer.DashboardScreen'
})
}
render() {
return ( <
View style = {
styles.container
} >
<
Text > SideDrawer < /Text> <
TouchableHighlight onPress = {
this.navigateToAbout.bind(this)
} >
<
Text > Click Me < /Text> <
/TouchableHighlight> <
/View>
)
}
}
Since pushing a screen is an action you perform on an existing navigation stack, you need to call it on your current component navigator object which you'll automagically get as a prop:
this.props.navigator.push({screen: 'drawer.DashboardScreen'})
I strongly suggest you migrate to react-native-navigation v2 as v1 has limited functionality and is no longer maintained.
Download source code from here(React Native Navigation)
App.js:
import React, {Component} from 'react';
import {createStackNavigator,createAppContainer} from 'react-navigation'
import HomeScreen from './Screens/HomeScreen';
import ProfileScreen from './Screens/ProfileScreen';
import ContactScreen from './Screens/ContactScreen';
import MainScreen from './Screens/MainScreen'
export default class App extends Component {
render() {
return <AppNavigationContainer/>
}
}
const AppNavigator = createStackNavigator({
Main:MainScreen,
Home: HomeScreen,
Profile: ProfileScreen,
Contact:ContactScreen
}, {
initialRouteName: 'Main',
navigationOptions: {
headerTitleStyle: {
fontWeight: "bold",
color: "red",
},
headerTintColor: "red"
}
})
const AppNavigationContainer = createAppContainer(AppNavigator);
MOVE FROM ONE SCREEN TO ANOTHER:
this.props.navigation.navigate('Profile')
Thanks!

react-navigation StackNavigator reset when update Redux state

need some help here, i having some problem when using redux with react navigation.
Whenever i update state in redux, the react-navigation will reset to initialRouteName in my DrawerNavigator which is Home
How can i stay on that screen after this.props.dispatch to update Redux state?
Is there any step that i should do when integrate redux with react-navigation?
Thank you so much for any help. Appreciate it
App.js
This is where i declare my StackNavigator, my DrawerNavigator is
nested in StackNavigator
import React, { Component } from "react";
import { connect, Provider } from "react-redux";
import { Platform, BackHandler, View, Text } from "react-native";
import { Root, StyleProvider, StatusBar } from "native-base";
import { StackNavigator, NavigationActions } from "react-navigation";
import Drawer from "./Drawer";
import AuthNavigator from "./components/login/authNavigator";
import Home from "./components/home";
import Settings from "./components/settings";
import UserProfile from "./components/userProfile";
import getTheme from "../native-base-theme/components";
const AppNavigator = token => {
return StackNavigator(
{
Drawer: { screen: Drawer },
Login: { screen: Login },
Home: { screen: Home },
AuthNavigator: { screen: AuthNavigator },
UserProfile: { screen: UserProfile }
},
{
initialRouteName: token ? "Drawer" : "AuthNavigator",
stateName: "MainNav",
headerMode: "none"
}
);
};
class App extends Component {
constructor(props) {
super(props);
this.state = {
isReady: false
};
}
componentDidMount() {
setTimeout(() => {
this.setState({ isReady: true });
}, 500);
}
render() {
let token = null;
const users = this.props.auth.users;
const index = this.props.auth.defaultUserIndex;
if (users.length > 0) {
token = users[index].token;
}
const Layout = AppNavigator(token);
return (
<Root>
<StyleProvider style={getTheme()}>{this.state.isReady ? <Layout /> : <View />}</StyleProvider>
</Root>
);
}
}
var mapStateToProps = state => {
return {
auth: state.auth
};
};
module.exports = connect(mapStateToProps)(App);
Drawer.js
This is my Drawer, whenever i update Redux state, the app will pop back
to the initialRouteName of DrawerNavigator which is Home
import React from "react";
import { DrawerNavigator, StackNavigator } from "react-navigation";
import { Dimensions } from "react-native";
import SideBar from "./components/sidebar";
import Home from "./components/home/";
import Settings from "./components/settings/";
const deviceHeight = Dimensions.get("window").height;
const deviceWidth = Dimensions.get("window").width;
const Drawer = DrawerNavigator(
{
Home: { screen: Home },
Settings: { screen: Settings },
CompanyProfile: { screen: CompanyProfile }
},
{
initialRouteName: "Home",
contentOptions: {
activeTintColor: "#e91e63"
},
contentComponent: props => <SideBar {...props} />,
drawerWidth: deviceWidth - 100
}
);
export default Drawer;
Reducer.js
const defaultState = {
users: [],
defaultUserIndex: 0
};
const defaultUserState = {
phoneNumber: undefined,
email: undefined,
name: undefined,
userId: undefined,
token: undefined,
avatar: undefined
};
module.exports = (state = defaultState, action) => {
console.log("reducer state: ", state);
switch (action.type) {
case "AUTH_USER":
case "UNAUTH_USER":
case "UPDATE_AVATAR":
case "UPDATE_PHONENUMBER":
case "UPDATE_PERSONALDETAILS":
return { ...state, users: user(state.defaultUserIndex, state.users, action) };
case "CLEAR_STATE":
return defaultState;
default:
return state;
}
};
function user(defaultUserIndex, state = [], action) {
const newState = [...state];
switch (action.type) {
case "AUTH_USER":
return [
...state,
{
phoneNumber: action.phoneNumber,
name: action.name,
email: action.email,
userId: action.userId,
token: action.token,
avatar: action.avatar,
}
];
case "UNAUTH_USER":
return state.filter(item => item.token !== action.token);
case "UPDATE_AVATAR":
newState[defaultUserIndex].avatar = action.avatar;
return newState;
case "UPDATE_PERSONALDETAILS":
newState[defaultUserIndex].name = action.name;
newState[defaultUserIndex].email = action.email;
return newState;
default:
return state;
}
}
In your case: you create new AppNavigator on each render invoke. And each of instance have new navigation state.
You can fix it by moving initialization to constructor, so every next render it will use same object.
constructor(props) {
super(props);
this.state = {
isReady: false
};
const token = //sometihng
this.navigator = AppNavigator(token)
}
Also: explore examples of redux integration in official docs https://github.com/react-community/react-navigation/blob/master/docs/guides/Redux-Integration.md