this.props.navigation.navigate() doesn't work (using WithNavigation) - reactjs

I have implemented a pop-up component in a header with More Options button. This button is available only on the UserProfileScreen. When you press on it you see two buttons: 'Settings' and 'Log out'. The issue is related to Settings button.
Expected behavior:
Press on Settings button -> this.props.navigation.navigate('SettingsNavigator') is called -> SettingsScreen is shown
Actual behavior:
Press on Settings button -> console.warn('go to settings') is called and nothing more (you are not redirected to SettingsScreen).
SettingsNavigator.js
import React from 'react';
import { createStackNavigator } from 'react-navigation';
import SettingsScreen from './SettingsScreen';
import EditProfileScreen from './EditProfileScreen';
import RemoveProfileScreen from './RemoveProfileScreen';
const SettingsNavigator = createStackNavigator({
SettingsScreen: SettingsScreen,
EditProfile: EditProfileScreen,
RemoveProfile: RemoveProfileScreen
}, {
initialRouteName: 'SettingsScreen'
});
export default SettingsNavigator;
optionHeaderButton.js
import { withNavigation } from 'react-navigation';
...
class OptionsHeaderButton extends Component {
...
navigateToSettings = () => {
console.warn('go to settings')
this.props.navigation.navigate('SettingsNavigator');
}
render() {
return(
<Menu onSelect = {value => {
switch(value) {
case 'Settings':
this.navigateToSettings();
break;
case 'Logout':
this.onLogOutPress();
break;
}
}}>
<MenuTrigger>
<Icon name = 'dots-vertical' size = {24} color = {text}/>
</MenuTrigger>
<MenuOptions style = {optionsHeaderButtonStyle.menuWrapper}>
<MenuOption value = {'Settings'}>
<View style = {optionsHeaderButtonStyle.optionWrapper}>
<View style = {optionsHeaderButtonStyle.optionIcon}>
<IconSimpleLine name = 'settings' size = {12} color = {text}/>
</View>
<Text style = {[optionsHeaderButtonStyle.option, optionsHeaderButtonStyle.lastOption]}>
Settings
</Text>
</View>
</MenuOption>
<MenuOption value = {'Logout'}>
<View style = {optionsHeaderButtonStyle.optionWrapper}>
<View style = {optionsHeaderButtonStyle.optionIcon}>
<IconSimpleLine name = 'logout' size = {12} color = {text}/>
</View>
<Text style = {[optionsHeaderButtonStyle.option, optionsHeaderButtonStyle.lastOption]}>
Logout
</Text>
</View>
</MenuOption>
</MenuOptions>
</Menu>
)
}
}
export default withNavigation(OptionsHeaderButton);
OptionsHeaderButton is nested to a Header Component that is nested in UserProfile Component.
As far as I know using withNavigation() I can call navigation.navigate() from any component. So why doesn't it work?
UPDATE:
After investigation I found that this issue is related to a bug of React Navigation that was found on April of 2017. It means that you can use navigation.navigate() only inside one Stack. But I can't find any info about fix of this bug.

you need to pass screen name instead of navigator.
this.props.navigation.navigate('SettingsSecreen');

try this:
import { NavigationActions, withNavigation } from 'react-navigation';
navigateToSettings = () => {
const navigateAction = NavigationActions.navigate({ routeName: 'SettingsScreen' });
this.props.navigation.dispatch(navigateAction);
}

I had this issue but I have wired up my App.js slightly differently using stack navigation and drawer navigation (And switch navigation for auth) - mostly followed the react navigation docs:
App.js
const AuthStack = createStackNavigator({ Login: Login }, { headerMode: 'none' });
const ActivityStack = createStackNavigator({ Activity: ActivityScreen}, {headerMode: 'none'});
// setup my routing & config here
export default createAppContainer(createSwitchNavigator(
{
Auth: AuthStack,
ActivityStack: ActivityStack,
},
{
intitalRouteName: 'Auth',
},
));
In the screen I want to navigate from I wire up the constructor like this (to bind the navigation function):
FirstScreen.js
export default class FirstScreen extends React.Component {
constructor(props) {
super(props);
this.navigateToScreen = this.navigateToScreen.bind(this);
}
state = {
// state items here
}
navigateToScreen(screenName) {
this.props.navigation.navigate(screenName);
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.button} onPress=
{this.navigateToScreen("Activity")}>
<Text style={styles.loginText}>
ADD ACTIVITY
</Text>
</TouchableOpacity>
</View>
Note how I am calling the navigation, I am only passing in the route alias I gave earlier in the App.js file:
Activity: ActivityStack,
So I would write this.navigateToScreen("Activity") from the calling point passing in the alias not the screen name or stack name.
This navigates correctly to the screen and utilizes stack navigation I defined earlier in App.js.
Hope this makes sense (parent child hierarchy is achieved by nesting the various navigation features like switch, stack and drawer to achieve your desired results.

in react-navigation Version: 5.x withNavigation is not working. To fix this issue try this.
import { useNavigation } from '#react-navigation/native';
const navigation = useNavigation();
navigation.navigate("DetailsScreen")
no need to warp the component in withNavigation.
Hope this may helps.

Related

Using React Navigation for a Navigator inside a Screen?

In my react native app, I have a stack navigator as so:
const AppStack = createStackNavigator(
{
Tabs: AppTabs,
Account: AccountScreen,
Profile: ProfileScreen,
Post: PostScreen,
},
);
The ProfileScreen contains some header content and another TabNavigator, in a layout similar to when you click on a channel in Youtube:
const ProfileTabs = createMaterialTopTabNavigator(
{
Home: ProfileHome,
Posts: ProfileMade,
},
);
const ProfileNav = createAppContainer(ProfileTabs);
class ProfileScreen extends React.Component {
render() {
return (
<View style={{ flex: 1 }}>
<View>Some header content</View>
<ProfileNav />
</View>
);
}
}
My problem is that I am unable to navigate from inside the ProfileTabs navigator to a screen in the original stacknavigator. E.g. if a user clicks on a post in the ProfileMade screen, I want to navigate them to the PostScreen.
Please can someone tell me what should be changed? Thanks
You can navigate to another stack sending the name of stack on the .navigate() method:
navigation.navigate('StackName', {
screen: 'YourScreenName'
});

Problem to return component from StackNavigator in react-native. Get a blank screen but cosole.log is OK

What I want to do is wrapp on the very top of my app a switch navigator named MainNavigator which redirect to a LoadingScreen who determine if user is Logged or no and navigate to AuthNavigator or AppNavigator.
AuthNavigator himself is a stackNavigator who display screens 'LoginScreen' or 'SignUpScreen'.
AppNavigator is a DrawerNavigator with multiple screens (Profile, Home...).
The problem I encouter is that the return method seems to work, because I can see the logs in my console from the 'LoginScreen' component but on the screen, the only return is a white screen.. When I call directly my 'LoginScreen' component from the LoadingScreen component by using :
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away. switched to develop the app
this.props.navigation.navigate(userToken ? 'App' : 'LoginScreen');
};
I had the LoginScreen component rendered on my device.
I know the problem come from my AuthNavigator and AppNavigator because I can return screens components from AuthLoading or MainNavigator
Here some code :
* Main Navigator: entry point of the app
* Redirect to AuthLoadingScreen by default,
* if user is signed (AsyncStorage) or successly signIn
* redirect to AppNavigator else if user isn't SignIn
* go to AuthNavigator
import { createAppContainer, createSwitchNavigator } from 'react-
navigation';
import AuthNavigator from './auth/AuthNavigator'
import AppNavigator from './app/AppNavigator'
import AuthLoadingScreen from '../screens/auth/AuthLoadingScreen'
export default createAppContainer(createSwitchNavigator(
{
// You could add another route here for authentication.
// Read more at https://reactnavigation.org/docs/en/auth-flow.html
AuthLoading: AuthLoadingScreen,
App: AppNavigator,
Auth: AuthNavigator,
},
{
initialRouteName: 'AuthLoading',
}
));
AuthLoading :
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
StatusBar,
StyleSheet,
View,
Text
} from 'react-native';
export default class AuthLoadingScreen extends React.Component {
constructor(props) {
super(props);
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away. switched to develop the app
this.props.navigation.navigate(userToken ? 'App' : 'Auth');
};
// Render any loading content that you like here
render() {
return (
<View>
<Text>Loading</Text>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
AuthNavigator:
import { createStackNavigator } from 'react-navigation';
import LoginScreen from '../../screens/auth/LoginScreen';
import SignUpScreen from '../../screens/auth/SignUpScreen';
export default createStackNavigator({
Login : {
screen : LoginScreen
},
SignUp: {
screen : SignUpScreen
}
},
{
initialRouteName: 'Login',
})
LoginScreen:
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
export default class LoginScreen extends Component {
render() {
console.log('LOGIN SCREEN')
return (
<View style={styles.container}>
<Text style={styles.text}>Login</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
text : {
fontSize: 20,
}
});
I can't find the error, but I know it comes from the AuthNavigator and AppNavigator. I can see the 'LOGIN SCREEN' or 'APP SCREEN' if I set the AuthLoading route to render App in my console but nothing on the screen, when I init a new route LoginScreen in my MainNavigator and navigate to that route from the AuthLoading it works, I have a beautiful text displayed on my screen.
I Know it's probably an insignifiant error but I spend hours to fix this by myself and can't find, so if someone can help me it would be nice!
Thanks by advance!
Problem solved... Because at the top of my app in App.js I wrapped my MainNavigator like that
<View>
<StatusBar hidden={true} />
<MainNavigator />
</View>
So I remove the view and statusbar... and it works. 4 days to figure it out... I love it! :')

Unable to use StackNavigator in ReactNative

I'm trying to create a simple navigation in the following page:
Page1:
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, Alert } from 'react-native';
import { StackNavigator } from 'react-navigation';
import { connect } from 'react-redux';
var styles = require('./styles');
class Page1 extends Component {
static navigationOptions = {
header: null
};
componentDidMount() { // <== Edited
setTimeout(function() {
const { navigate } = this.props.navigation;
},4000);
}
render() {
const { navigate } = this.props.navigation; // <= Getting error here
const { fname, lname } = this.props.person;
return (
<View style={styles.container}>
<Text style={styles.welcome}>
From Page 1 - {lname} { fname}
</Text>
<TouchableOpacity
// onPress={() => this.props.navigation.navigate('Page2', { user: 'Lucy' }) }
>
<Text style={styles.welcome}> click for Page 2</Text>
</TouchableOpacity>
</View>
);
}
}
function mapStateToProps(state) {
return {
person:state.person
}
}
export default connect(mapStateToProps) (Page1);
Page 3:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import Page1 from './Page1'
import { Provider, connect } from 'react-redux'
import configureStore from './store'
const store = configureStore()
export default class Page3 extends Component {
render() {
return (
<Provider store={store}>
<Page1 />
</Provider>
);
}
}
And in the index page I'm importing Page1, Page2 and Page3 and :
const SimpleApp = StackNavigator({
Page1: { screen: Page1 },
Page2: { screen: Page2 },
Page3: { screen: Page3 },
});
AppRegistry.registerComponent('my03', () => SimpleApp);
The app works fine unless I comment const { navigate } = this.props.navigation;. I get the following error:
Undefined is not an object (evaluating 'this.props.navigation.navigate')
I also tried:
onPress={() => this.props.navigation.navigate('Page2', { user: 'Lucy' }) } and
onPress={() => navigation.navigate('Page2', { user: 'Lucy' }) }
Not really sure why I tried it but was going to figure it out if it worked. It did not.
I'm trying to use ReactNavigation without creating the reducer for it, cause I did not understand this part even after trying for 2 days.
Why is this failing here? Please help.
Many thanks.
UPDATE1
import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';
import { StackNavigator } from 'react-navigation';
import Page1 from './src/components/Page1'
import Page2 from './src/components/Page2'
import Page3 from './src/components/Page3'
const SimpleApp = StackNavigator({
Page3: { screen: Page3 },
Page2: { screen: Page2 },
Page1: { screen: Page1 },
});
AppRegistry.registerComponent('my03', () => SimpleApp);
Short answer. Just do this. <Page1 navigation={this.props.navigation} />
Explanation - The reason why this.props.navigation is coming out undefined is that you're essentially using another instance of the Page 1 component inside your Page 3 and not the one that you used to initialize the StackNavigator with. So it's an entirely new Page 1 that is not coupled with the StackNavigator at all. If Page 1 would have been the starting component of your StackNavigator. Then this.props.navigation would not have been undefined which brings me to another point of interest.
Why would you ever nest Page 1 inside Page 3 but want the same page as a sibling to Page 3 inside your react navigation stack? The idea is to add components inside our StackNavigator as screens without nesting them together and we move from one screen to another using this.props.navigation.navigate inside any one of them. So, therefore, our Page 3 would have our navigation prop (since I'm assuming it gets loaded first through the StackNavigator directly) we just pass that prop to our nested Page 1 component and viola! You would now have access to this.props.navigation inside your Page 1.
Also, since your Page 3 has the <Provider > tag I'm assuming it's something more of a root. In that case, you're better off using <SimpleApp > in place of <Page1 > and keeping Page 1 as the starting point of your Stack. You can then register your root component as AppRegistry.registerComponent('my03', () => Page3);
The last piece of info, you can keep your redux state and your navigation completely decoupled from each other so use redux integration only when you're absolutely sure that you need your navigation state inside your redux store. A project which has both Redux and ReactNavigation doesn't mean that you have to integrate them both. They can be completely separate.
Phew! Hope it helps! :)

Pass TextInput props between different screens in React Navigation

I am totally lost on how to grab two parameters from a form screen and pass them via React Navigator and display them on the previous screen.
The app section in question works like this:
1. touchablehighlight to form screen.
2. input title and description and press submit onpress
3. the onpress runs a function that dispatches the parameters to the previous page via a key.
4. then returns back to the origin page, with the props on display.
I am having multiple issues with the process:
1. if I am understanding the docs correctly, each page has a unique key and i tried to find it via this.props.navigation.state.key, however unknown to me, on refresh the id number would change.
2. that leads to problem 2 where the function will run, but it will not redirect back to the original page.
3. i have tried .navigate line after .dispatch but it would open a new copy of the original page and not display the new props that supposively were passed down.
import React from 'react';
import styles from '../styling/style.js';
import { Text, View, Image, TouchableHighlight, TextInput, Alert } from 'react-native';
import { StackNavigator, NavigationActions } from 'react-navigation';
import Forms from './formGenerator';
export default class Workout extends React.Component {
constructor(props) {
super(props);
this.state = {
programTitle: '',
programDescription: ''
}
}
render() {
const {navigate} = this.props.navigation;
return (
<Image style={styles.workoutContainer, { flex: 1}} source={require("../images/HomepageBackground.jpg")}>
<View style={styles.workoutBody}>
<Text style={styles.workoutTextBody}> {this.state.programTitle}</Text>
<Text style={styles.workoutTextBody}>{this.state.programDescription}</Text>
</View>
<View style={styles.createButton}>
<TouchableHighlight onPress={Alert.alert(this.props.navigation.state.key)} style={styles.addButtonTouch} title='test' >
<Text style={styles.addButtonText}>+</Text>
</TouchableHighlight>
</View>
</Image>
);
}
// End of the render
}
import React from 'react';
import styles from '../styling/style.js';
import { Text, View, Image, TouchableHighlight, TextInput, Button, Alert } from 'react-native';
import Workout from './workouts';
import { NavigationActions } from 'react-navigation';
export default class Forms extends React.Component {
constructor(props) {
super(props);
this.state = {
programTitle: '',
programDescription: ''
}
}
render() {
const {goBack} = this.props.navigation;
const {params} = this.props.navigation.state;
return (
<Image style={styles.workoutContainer, { flex: 1}} source={require("../images/HomepageBackground.jpg")}>
<View style={styles.workoutBody}>
<Text style={styles.formHeader}>Program Title</Text>
<TextInput
autoFocus={true}
style={styles.formBody}
onChangeText={(programTitle) => this.setState({programTitle})}
placeholder='Title'
value={this.state.programTitle} />
<Text style={styles.formHeader}>Description (Ex. 4sets x 10reps)</Text>
<TextInput
autoFocus={true}
style={styles.formBody}
placeholder='Description'
onChangeText={(programDescription) => this.setState({programDescription})}
value={this.state.programDescription} />
<TouchableHighlight onPress={this.addProgram} style={styles.buttonBody} title="Add Program" >
<Text>Add Program</Text>
</TouchableHighlight>
</View>
</Image>
);
}
addProgram = () => {
Alert.alert(this.props.navigation.state.key);
this.setState({programTitle: ''});
this.setState({programDescription: ''});
const setParamsAction = NavigationActions.setParams({
params: { programTitle: this.state.programTitle, programDescription: this.state.programDescription },
key: ,
})
this.props.navigation.dispatch(setParamsAction)
};
}
If you are trying to get parameter from "Next Page", you could have two approaches.
1, save the params in AsyncStorage (suggested)
2, using navigation setParams function with the params
const setParamsAction = NavigationActions.setParams({
params: { title: 'Hello' },
key: 'screen-123',
})
this.props.navigation.dispatch(setParamsAction)
https://reactnavigation.org/docs/navigators/navigation-actions
You are just trying to display information from the Forms class on the Workout class, correct??
From your Workout class, create a function that update's it's state.
updateWorkoutState = (programTitle,programDescription) => this.setState(programTitle,programDescription)
Pass that function through to the Forms class when you push that route.
this.props.navigation.navigate('Forms',{updateWorkoutState: this.updateWorkoutState}
Once your conditions are met on the Forms class and you want to update the Workout component, call it with this.props.navigation.state.params.updateWorkoutState(val1,val2)
Do not use AsyncStorage for this.

How to get current navigation state

I am using React Navigation's Tab Navigator from https://reactnavigation.org/docs/navigators/tab, when I switch between the Tab Screens I don't get any navigation state in this.props.navigation.
Tab Navigator:
import React, { Component } from 'react';
import { View, Text, Image} from 'react-native';
import DashboardTabScreen from 'FinanceBakerZ/src/components/dashboard/DashboardTabScreen';
import { TabNavigator } from 'react-navigation';
render() {
console.log(this.props.navigation);
return (
<View>
<DashboardTabNavigator />
</View>
);
}
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
},
THISWEEK: {
screen: DashboardTabScreen
}
});
DASHBOARD SCREEN:
import React, { Component } from 'react';
import { View, Text} from 'react-native';
export default class DashboardTabScreen extends Component {
constructor(props) {
super(props);
this.state = {};
console.log('props', props);
}
render() {
console.log('props', this.props);
return (
<View style={{flex: 1}}>
<Text>Checking!</Text>
</View>
);
}
}
I get props at Dashboard Screen when it renders the component first but I don't get props when I switch the tabs.
I need to get the current Screen name i.e TODAY or THISWEEK.
Your problem is about "Screen Tracking", react-navigation has an officially guide for this. you can use onNavigationStateChange to track the screen by using built-in navigation container or write a Redux middleware to track the screen if you want to integrate with Redux. More detail can be found at the officially guide: Screen-Tracking. Below is a sample code from the guide by using onNavigationStateChange:
import { GoogleAnalyticsTracker } from 'react-native-google-analytics-bridge';
const tracker = new GoogleAnalyticsTracker(GA_TRACKING_ID);
// gets the current screen from navigation state
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
const AppNavigator = StackNavigator(AppRouteConfigs);
export default () => (
<AppNavigator
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
// the line below uses the Google Analytics tracker
// change the tracker here to use other Mobile analytics SDK.
tracker.trackScreenView(currentScreen);
}
}}
/>
);
Check all properties first, like
<Text>{JSON.stringify(this.props, null, 2)}</Text>
Above json array will show you current state of navigation under routeName index i.e.
this.props.navigation.state.routeName
Have you tried to define navigationOptions in your route object?
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: {
title: 'TODAY',
},
},
})
You can also set navigationOptions to a callback that will be invoked with the navigation object.
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: ({ navigation }) => ({
title: 'TODAY',
navigationState: navigation.state,
})
},
})
Read more about navigationOptions https://reactnavigation.org/docs/navigators/
Answer as of React Navigation v6
Depending on whether you want to trigger re-renders on value changes:
const state = navigation.getState();
or
const state = useNavigationState(state => state);
Reference:
https://reactnavigation.org/docs/use-navigation-state#how-is-usenavigationstate-different-from-navigationgetstate

Resources