react-navigation, dispatch action to change scene - scene not showing - react-native

I have setup my substeps navigator below, the first scene, PCDrinkingDays works fine. However, when I click to dispatch an action, from that component to change scene:
onDone = () => {
this.props.dispatch(navigate(scenes.PC_HELP_ME_CALCULATE));
};`
The PCHelpMeCalculate, doesn't appear, although I can see it renders in the background by setting breakpoints.
My navigator is below. The other scenes seem ok, its just moving from the first scene that seems to be the problem. Can anyone help? Why isnt the component showing?
import { SubStepsNavigator } from '../../../navigators';
import PCDrinkingDays from '../PCDrinkingDays';
import PCHelpMeCalculate from '../PCHelpMeCalculate';
import PCUnitsCalculatorWhat from '../PCUnitsCalculatorWhat';
import PCUnitsCalculatorSize from '../PCUnitsCalculatorSize';
import PCUnitsCalculatorStrength from '../PCUnitsCalculatorStrength';
import PCUnitsCalculatorDrinksNumber from '../PCUnitsCalculatorDrinksNumber';
import PCUnitsCalculatorTable from '../PCUnitsCalculatorTable';
import * as scenes from '../../../scenes';
const PCDrinkingScenes = {
[scenes.PC_DRINKING_DAYS]: { screen: PCDrinkingDays },
[scenes.PC_HELP_ME_CALCULATE]: { screen: PCHelpMeCalculate },
[scenes.PC_UNITS_CALCULATOR_WHAT]: { screen: PCUnitsCalculatorWhat },
[scenes.PC_UNITS_CALCULATOR_SIZE]: { screen: PCUnitsCalculatorSize },
[scenes.PC_UNITS_CALCULATOR_STRENGTH]: { screen: PCUnitsCalculatorStrength },
[scenes.PC_UNITS_CALCULATOR_DRINKS_NUMBER]: {
screen: PCUnitsCalculatorDrinksNumber,
},
[scenes.PC_UNITS_CALCULATOR_TABLE]: { screen: PCUnitsCalculatorTable },
};
function getStep(routeName) {
switch (routeName) {
case scenes.PC_DRINKING_DAYS:
case scenes.PC_HELP_ME_CALCULATE:
case scenes.PC_UNITS_CALCULATOR_WHAT:
case scenes.PC_UNITS_CALCULATOR_SIZE:
case scenes.PC_UNITS_CALCULATOR_STRENGTH:
case scenes.PC_UNITS_CALCULATOR_DRINKS_NUMBER:
case scenes.PC_UNITS_CALCULATOR_TABLE:
return 1;
default:
console.log(`ERR not found step for route ${routeName}`);
return -1;
}
}
const stepsOptions = {
nSteps: 2,
getStep,
};
const PCDrinkingNavigator = SubStepsNavigator(
PCDrinkingScenes,
{ navigationOptions: { gesturesEnabled: false }, headerMode: 'none' },
stepsOptions
);
export default PCDrinkingNavigator;
I dispatch my action in my component:
class PCDrinkingDays extends Component {
onDone = () => {
this.props.dispatch(navigate(scenes.PC_HELP_ME_CALCULATE));
};
render() {
return (
<SceneSelectNumber
goBack={this.goBack}
onChangeNumber={this.onChangeNumber}
selectedNumberValue={this.props.daysInAweek}
onDone={this.onDone}
sceneConfig={sceneConfig}
/>
);
}
}

You seem to dispatch your action nowhere. You should use
this.props.navigation.dispatch(navigate(scenes.PC_HELP_ME_CALCULATE));
Or you could even use
this.props.navigation.navigate(scenes.PC_HELP_ME_CALCULATE);

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

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 native: reset route, back to home page

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?

Remove screen from stack navigator

I’ve observed Back button logic works seeing the sequence of screens in the stack. I’ve a Drawer navigator placed inside stack navigator.
On top of the stack I’ve Splash screen. On Dashboard when I press back button it takes me to splash screen.
How can I remove splash screen from stack after entering in the app, So when I press back button Dashboard it will EXIT the app instead of taking to SPLASH SCREEN.
/* #flow */
import React from "react";
import { Platform, Text } from "react-native";
import { Root } from "native-base";
import { StackNavigator, DrawerNavigator} from "react-navigation";
import Register from "./components/Register";
import Available from "./components/Available";
import Splash from "./components/splash/“;
import SideBar from "./components/sidebar";
import Discover from "./components/Discover/";
import Dashboard from "./components/Dashboard/";
import Contact from "./components/Contact"
const Drawer = DrawerNavigator(
{
Dashboard: { screen: Dashboard },
Discover: { screen: Discover },
Contact: { screen: Contact},
},
{
navigationOptions: {
gesturesEnabled: false,
},
initialRouteName: "Dashboard",
contentOptions: {
activeTintColor: "#e91e63"
},
drawerPosition: 'right',
contentComponent: props => <SideBar {...props} />
}
);
const AppNavigator = StackNavigator(
{
Splash: { screen: Splash },
Drawer: { screen: Drawer },
Available: { screen: Available },
Register: { screen: Register },
},
{
// initialRouteName: “Splash”,
headerMode: "none",
}
);
export default () =>
<Root>
<AppNavigator />
</Root>;
One solution would be to reset the stack inside the splash screen component and redirect the user to the screen that you prefer:
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Drawer'})
]
})
this.props.navigation.dispatch(resetAction)
For newer versions of react-navigation :
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Profile' })],
});
this.props.navigation.dispatch(resetAction);
Link to the official documentation
One of the simplest solutions would be, replace current screen with new screen so user won't be allowed to go back.
const SplashScreen: () => {
...
navigation.replace("LoginScreen"); // Move forward and Remove this screen from stack
see https://reactnavigation.org/docs/stack-actions/#replace
I solved it using this
code:
const prevGetStateForActionHomeStack = HomeStack.router.getStateForAction;
HomeStack.router = {
...HomeStack.router,
getStateForAction(action, state) {
if (state && action.type === 'ReplaceCurrentScreen') {
const routes = state.routes.slice(0, state.routes.length - 1);
routes.push(action);
return {
...state,
routes,
index: routes.length - 1,
};
}
return prevGetStateForActionHomeStack(action, state);
},
};
replaceScreen = () => {
const { locations, position } = this.props.navigation.state.params;
this.props.navigation.dispatch({
key: 'NearMeMap',
type: 'ReplaceCurrentScreen',
routeName: 'NearMeMap',
params: { locations, position },
});
};
Also if you need in-depth explanation of the code, watch this short video here
In react-navigation 5.x version. If you want to open new screen and remove previous stack screen, you can write: -
navigation.dispatch(StackActions.replace('screen_name', {params: {}}));
The replace action allows to replace a route in the navigation state. You can import it as:-
import { StackActions } from '#react-navigation/native';
If you want to perform other actions on stack navigation check out this link-
https://reactnavigation.org/docs/stack-actions
useEffect(() => {
setTimeout(() => {
navigation.reset({
index:0,
routes:[
{
name:"Welcome",
// params:{key:'param'},
},
],
});
}, 1000);},[]);
You can also trigger the this onPress like:
onPress={()=>navigation.reset({
index:0,
routes:[
{
name:"NameOfScreen",
// params:{key:'param'},
},
],
})}

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