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! :)
Related
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! :')
i need help :).
my project is 2 pages in react native, MainPage and SoundRecord.
my init screen is MainPage and when i press the button 'take sound'
i move to another component to record sound(i move with react native navigation).
when i come back i want to return the filePath(where it save the file..).
i want to insert it to the state.
when i do this in MainPage:
this.state{
filePath: this.props.navigation.state.params.filePath
}
it give error:
undefined is not an object(evaluating 'this.props.navigation.state.params.filePath')
and i understand this because i start my project with MainPage and i dont have the filePath from the SoundRecord page.
what can i do?
can i do check if this.props.navigation.state.params.filePath !== undefined?
how to do it? i try almost everything...
i put the relevant code:
MainPage:
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import ImagePicker from 'react-native-image-picker';
import { RNS3 } from 'react-native-aws3';
import { aws } from './keys';
import SoundRecord from './SoundRecord'
export default class MainPage extends Component {
constructor(props){
super(props);
this.state={
file:'' ,
config:'',
filePath: '',
fileName: '',
tag:''
};
}
MoveToSoundRecordPage = () => {
this.props.navigation.navigate('SoundRecord');
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.MoveToSoundRecordPage}>
<Text>take sound</Text>
</TouchableOpacity>
{/* <SoundRecord takeSound={this.takeSound}/> */}
<Text>{this.state.fileName}</Text>
<TouchableOpacity onPress={this.UploadToAWS.bind(this)}>
<Text>Upload To Aws</Text>
</TouchableOpacity>
</View>
);
}
}
SoundRecord when i finish to record i send the filePath like this:
finishRecord = (filePath) => {
this.props.navigation.navigate('MainPage',{filePath});
}
thank!
If you want to update something on the previous page then you can do it in the following way.
Create a function in the MainPage that updates the state with the new filepath.
Pass the function as as a param.
Use the passed function in SoundRecord to update the state in the MainPage
MainPage
In your MainPage add the following
sendFilepath = (filePath) => {
this.setState({filePath})
}
Update the MoveToSoundRecordPage function to take the sendFilepath as a parameter:
MoveToSoundRecordPage = () => {
this.props.navigation.navigate('SoundRecord', { sendFilepath: this.sendFilepath });
}
SoundRecord
Then in the SoundRecord page you want to update the finishRecord so that it calls the function that you have passed.
finishRecord = (filePath) => {
this.props.navigation.state.params.sendFilepath(filePath)
this.props.navigation.goBack();
}
Snack
Here is a snack https://snack.expo.io/#andypandy/navigation-passing-function that shows passing a function called sendFilepath from Screen1 to Screen2. Then in Screen2 it then calls the function that was passed and this updates the state in Screen1.
Please try this. It may help you
Add this in MainPage screen:
componentWillMount() {
const filePath = this.props.navigation.getParam('filePath', '');
this.setState({ filePath:filePath })
}
I am using createMaterialTopTabNavigator from react-navigation in which i have two separate screens UpdatesStack and ShopsStack and i want to navigate to other screen from these screens so i written like <Toptab navigation={this.props.navigation} /> and it showing me following red screen error.
And if i write like <Toptab /> then there is no error but i am not able to navigate.
so how can i solve this problem and able to navigate.
code
class Parenthome extends Component {
render() {
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<ToolbarAndroid
style={styles.toolbar}
title="Toolbar"
titleColor="#ff6600"
/>
<Toptab navigation={this.props.navigation} />
</View>
);
}
}
const UpdatesStack = createStackNavigator(
{
Updates: { screen: Home }
},
{
initialRouteName: "Updates"
}
);
const ShopsStack = createStackNavigator(
{
Shops: { screen: Conshop }
},
{
initialRouteName: "Shops"
}
);
const Toptab = createMaterialTopTabNavigator({
Updatestab: { screen: UpdatesStack },
Shopstab: { screen: ShopsStack }
});
export default Parenthome;
I know it's late but just to answer for those who stumble on this from Search Engines:
Why don't you export default TopTab itself. There seems no need to wrap TopTab with ParentTheme component in your use case. You can style the TopTab navigator itself and render it like any other component.
If you must wrap the TopTab you need to have the router from the TopTab accessible, in addition to the navigation prop. This way they both refer to the same router. Simply put, add in ParentTheme:
static router = TopTab.router;
Check out Custom Navigators for more info. https://reactnavigation.org/docs/en/custom-navigators.html
if you are using functional react components with hooks you won't be able to declare a static variable inside your components because they are not JS classes.
Instead declare the router variable as follows:
const reactComponent = (props) => {
/* your component logic and render here */
}
reactComponent.router = TopTab.router; //equivalent to static variable inside a class
export default reactComponent
I created a multiscreen app using React Navigator following this example:
import {
createStackNavigator,
} from 'react-navigation';
const App = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
export default App;
Now I'd like to add a global configuration state using the new builtin context api, so I can have some common data which can be manipulated and displayed from multiple screens.
The problem is context apparently requires components having a common parent component, so that context can be passed down to child components.
How can I implement this using screens which do not share a common parent as far as I know, because they are managed by react navigator?
You can make it like this.
Create new file: GlobalContext.js
import React from 'react';
const GlobalContext = React.createContext({});
export class GlobalContextProvider extends React.Component {
state = {
isOnline: true
}
switchToOnline = () => {
this.setState({ isOnline: true });
}
switchToOffline = () => {
this.setState({ isOnline: false });
}
render () {
return (
<GlobalContext.Provider
value={{
...this.state,
switchToOnline: this.switchToOnline,
switchToOffline: this.switchToOffline
}}
>
{this.props.children}
</GlobalContext.Provider>
)
}
}
// create the consumer as higher order component
export const withGlobalContext = ChildComponent => props => (
<GlobalContext.Consumer>
{
context => <ChildComponent {...props} global={context} />
}
</GlobalContext.Consumer>
);
On index.js wrap your root component with context provider component.
<GlobalContextProvider>
<App />
</GlobalContextProvider>
Then on your screen HomeScreen.js use the consumer component like this.
import React from 'react';
import { View, Text } from 'react-native';
import { withGlobalContext } from './GlobalContext';
class HomeScreen extends React.Component {
render () {
return (
<View>
<Text>Is online: {this.props.global.isOnline}</Text>
</View>
)
}
}
export default withGlobalContext(HomeScreen);
You can also create multiple context provider to separate your concerns, and use the HOC consumer on the screen you want.
This answer takes in consideration react-navigation package.
You have to wrap your App component with the ContextProvider in order to have access to your context on both screens.
import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import ProfileContextProvider from '../some/path/ProfileContextProvider'
const RootStack = createStackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
});
const AppContainer = createAppContainer(RootStack)
const App = () => {
return (
<ProfileContextProvider>
<AppContainer />
</ProfileContextProvider>);
}
https://wix.github.io/react-native-navigation/docs/third-party-react-context/
As RNN screens are not part of the same component tree, updating the values in the shared context does not trigger a re-render across all screens. However you can still use the React.Context per RNN screen component tree.
If you need to trigger a re-render across all screens, there are many popular third party libraries such as MobX or Redux.
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