I'm currently figuring out how to reuse a navigation which is declared in it's own class for multiple screens or to put it in another way: If my approach isn't clever react wise, is there another, better way to create a navigation that is reused on multiple screens?
Whenever I'm trying to click a button in the navigation I get an error "undefined is not an object (evaluating _this2.props.navigation.navigate). So I guess that I'm missing something about this.props in the Navigation.js.
I'm using react-navigation because it has been recommended on SO and in the react-native documentation as the way to go.
App.js
import React from 'react';
import {createStackNavigator} from 'react-navigation';
import HomeScreen from './screens/home/HomeScreen'
import ProfileScreen from './screens/profile/ProfileScreen'
import SettingsScreen from './screens/settings/SettingsScreen'
const RootStack = createStackNavigator(
{
Home: HomeScreen,
Profile: ProfileScreen,
Settings: SettingsScreen,
},
{
initialRouteName: 'Home',
}
);
export default class App extends React.Component {
render() {
return <RootStack />;
}
}
Navigation.js - contains the planned navigation for all screens
import React from 'react';
import {StyleSheet, View, TouchableOpacity, Text} from 'react-native';
class Navigation extends React.Component {
render() {
return (
<View style={navigationStyles.footerWrapper}>
<View style={navigationStyles.footer}>
<TouchableOpacity style={navigationStyles.footerItem}
onPress={() => this.props.navigation.navigate('Home')}>
<Text style={navigationStyles.placeholderIcon}/>
</TouchableOpacity>
<TouchableOpacity style={navigationStyles.footerItem}
onPress={() => this.props.navigation.navigate('Profile')}>
<Text style={navigationStyles.placeholderIcon}/>
</TouchableOpacity>
<TouchableOpacity style={navigationStyles.footerItem}
onPress={() => this.props.navigation.navigate('Settings')}>
<Text style={navigationStyles.placeholderIcon}/>
</TouchableOpacity>
</View>
</View>
);
}
}
const navigationStyles = StyleSheet.create({
//
});
module.exports = Navigation;
HomeScreen.js - screen that should contain the navigation but displays an error when the onPress event is fired
import React from 'react';
import {StyleSheet, View, TouchableOpacity, Text} from 'react-native';
import styles from '../../common/customStyleSheet'
import Navigation from '../../components/navigation/Navigation';
class HomeScreen extends React.Component {
static navigationOptions = {
title: 'Home',
header: null,
};
render() {
const {navigate} = this.props.navigation;
return (
<View style={styles.container}>
<Text style={homeScreenStyles.paddingMedium}>HomeScreen.</Text>
<Navigation/>
</View>
);
}
}
const homeScreenStyles = StyleSheet.create({
paddingMedium: {
paddingTop: 200,
},
});
module.exports = HomeScreen;
your Navigation component won't automatically inherit the navigation prop from HomeScreen because it is just a subcomponent (it is not defined in the stack navigator like the HomeScreen is). So you need to pass the navigation as a prop to the Navigation component in your HomeScreen JSX.
// HomeScreen.js
render() {
return (
<View style={styles.container}>
<Text style={homeScreenStyles.paddingMedium}>HomeScreen.</Text>
<Navigation navigation={this.props.navigation}/>
</View>
);
}
Related
I am trying to use navigation in react native, but it gives me an error when I try use a component with navigate (LogIn) with the
<Login/>
tags.
It says that navigate is undified so I passed the navigation as a prop with no success
App.js
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { SafeAreaView, StyleSheet, Text, View } from 'react-native';
import Login from "./assets/code/Login.js";
import { NavigationContainer, StackActions } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import 'react-native-gesture-handler';
const Stack = createStackNavigator();
export default function App({ navigation }) {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Welcome to dog app, and I hate react"
component={HomePage}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
function HomePage({ navigation }) {
return (
<View style={styles.container}>
<View>
<Text> {""}Welcome to dogappcoolapp app</Text>
</View>
<View style={styles.blue}>
<Login navigate={ navigation } style={styles.blue} />
</View>
</View>
)
}
The error is in this line
<Login navigate={ navigation } style={styles.blue} />
The error is
The LogIn function is in
LogIn.js
import React, { useState } from 'react';
import { StyleSheet, Text, View, Button, Script, Alert } from 'react-native';
import { NavigationContainer, StackActions, } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import 'react-native-gesture-handler';
const Stack = createStackNavigator();
export function Login({navigation}) {
if (true)
return (
navigation.navigate('WelcomePage')
);
}
function WelcomePage () {
return (
<View>
<Button title="enter" />
<Text> dog app dog woof-app{"\n\n\n\n\n\n\n\n\n"}OMG!! YOU ARE LOGGED IN! WELCOME!{"\n\n\n\n\n"}</Text>
</View>
);
}
const styles = StyleSheet.create({
blue: {
flex: 2,
backgroundColor: "dodgerblue",
alignItems: 'center',
justifyContent: 'center',
},
});
export default Login;
If I remove all of the navigation prop and tags from the function LogIn, then I can use LogIn as a componnent with <LogIn/>, but not with navigation, I tried usinng it with
navigate={ navigation }
(As it is in the code that I posted)
and I tried without it, I keep getting similar errors.
How can I use LogIn as a component with </> tags while still having navigation component in it?
The logic is correct, in child components, you can pass a navigation prop to get access to navigation, but you are passing your navigation object to a prop called navigate <Login navigate={ navigation } style={styles.blue} />, no wonder it's undefined, you should receive it as navigate in your Login component.
export function Login({navigation}) { //<-- here you have navigation where the prop name that you pass is navigate.
so it should be
export function Login({navigate}) {
...
navigate.navigate('...')
or you should rename your prop to navigation and then your navigation.navigate won't be undefined anymore.
I have a Stack Navigator and some firebase functions in my app.js file. After implementing the Stack I am unable to use the log out button in the listScreen component because I found I cant export multiple in app.js. If I remove one export for app.js for example the Stack will work and vice versa.
App.js problem
//export navigation, container to wrap root navigator
export default createAppContainer(Switcher);
//PROBLEM cant have multiple exports, ether the Switcher or App class can export Individually
//class for app
export default class App extends Component {
Here is all the code in app.js
//Blue List is has been created by Ameer Yasin and Nick
import React, { Component } from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
//firebase
import firebase from './servers/firebase.js';
//navigation
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer } from 'react-navigation';
//components and sub component
import Login from './components/LoginPage.js';
import { SpinLoad } from './components/common/index.js';
import ListScreen from './components/ListScreen';
import AboutScreen from './components/AboutScreen';
// switch stack navigator
const Switcher = createStackNavigator(
{
//from listScreen to about screen
TaskPg: ListScreen,
AboutPg: AboutScreen
},
{
//the route of nav
initialRouteName: "TaskPg",
//header title
defaultNavigationOptions: {
title: 'BlueList'
}
}
)
//export navigation, container to wrap root navigator
export default createAppContainer(Switcher);
//PROBLEM cant have multiple exports, ether the Switcher or App class can export Individually
//class for app
export default class App extends Component {
//are u logged in set state to null
state = { loggedIn: null };
componentWillMount() {
//when logged in call this function
firebase.auth().onAuthStateChanged((user) => {
//logged in
if (user) {
this.setState({ loggedIn: true });
} //logged out
else {
this.setState({ loggedIn: false });
}
});
}
renderContent() {
//render content depending on auth status
switch (this.state.loggedIn) {
//goto list screen when logged in
case true: return (
<ListScreen />
)
//if not logged in goto log in page
case false: return <Login />;
//show loading icon by default
default: return <SpinLoad size='large' />
}
}
//render content on screen
render() {
return (
//SafeAreaView container for content
<SafeAreaView style={styles.container}>
{this.renderContent()}
</SafeAreaView>
);
}
};
//styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Here is the list screen where the Stack Navigator is. Again, the navigator works only if I remove the export for App in app.js therefore the sigh out wont work... I need both to work.
const ListScreen = props => {
return (
<View style={styles.container}>
{/* add task component with date picker */}
<AddItemModel />
{/* button pressed to goto About Screen */}
<TouchableOpacity onPress={() => props.navigation.navigate('AboutPg')}>
<Text style={styles.aboutBtn}>About App</Text>
</TouchableOpacity>
{/* sign out button linked to firebase log out */}
<TouchableOpacity onPress={() => firebase.auth().signOut()} >
<Text style={styles.button} >Sign Out</Text>
</TouchableOpacity>
</View>
);
}
I would really appreciate some help, I tried many methods for multiple exports as well as moving the firebase login functions in another component and still had the same issue.
UPDATE
Here is what was suggested, I tried to add the import statement with the correct path and it wont work.
import React from 'react';
import firebase from '../servers/firebase';
import { Text, StyleSheet, View, TouchableOpacity } from 'react-native';
import AddItemModel from './common/AddItemModel';
//what was suggested
import {default as AppNavigator, App} from "../App"
const ListScreen = props => {
return (
<View style={styles.container}>
{/* add task component with date picker */}
<AddItemModel />
{/* button pressed to goto About Screen */}
<TouchableOpacity onPress={() => props.navigation.navigate('AboutPg')}>
<Text style={styles.aboutBtn}>About App</Text>
</TouchableOpacity>
{/* sign out button linked to firebase log out */}
<TouchableOpacity onPress={() => firebase.auth().signOut()} >
<Text style={styles.button} >Sign Out</Text>
</TouchableOpacity>
</View>
);
}
File directory structure screenshot. The two relivent files are App.js and ListScreen.js
You can absolutely have multiple exports, you just can't have multiple default exports. Try import {default as AppNavigator, App} from "app.js".
So something like:
// app.js
export default createAppContainer(Switcher);
export class App extends Component {}
// component.js
import {default as AppNavigator, App} from "app.js"
const MyComponent = () => (
<div>
<AppNavigator/>
<App/>
</div>
);
I am new to react native and I have been trying to create a header and pull out menu using createDrawerNavigator.
When running my code I get the following error.
Error: The component for route 'Home' must be a React component. For example:
import MyScreen from './MyScreen';
Home: MyScreen,
}
You can also use a navigator:
import MyNavigator from './MyNavigator';
Home: MyNavigator,
}
Here is my app.js file:
import React from 'react';
import { Button, View, Text } from 'react-native';
import { createAppContainer } from 'react-navigation';
import MyDrawerNavigator from './components/MyDrawerNavigator';
const MyApp = createAppContainer(MyDrawerNavigator);
export default class App extends React.Component {
render() {
return <MyApp />;
}
}
My HomeScreen.js file
import React from 'react';
import { Text, Button } from 'react-native';
import LogoTitle from './LogoTitle';
class MyHomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerLeft: (
<Button
onPress={() => this.props.navigation.navigate('DrawerToggle')}
title={'Menu'}
/>
),
};
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
}
export default MyHomeScreen;
My MyDrawerNavigator.js file
import React from 'react';
import { Button, Platform, Image, View, Text } from 'react-native';
import { createDrawerNavigator } from 'react-navigation-drawer';
import MyHomeScreen from './HomeScreen';
import DetailsScreen from './DetailScreen';
const MyDrawerNavigator = createDrawerNavigator(
{
Home: MyHomeScreen,
Details: DetailsScreen,
}, {
drawerPosition: 'right',
contentOptions: {
activeTintColor: '#000',
},
});
export default MyDrawerNavigator
The are 2 problems here:
1) You are not exporting your MyDrawerNavigator just add:
export default MyDrawerNavigator
to your MyDrawerNavigator.js file
2) You are not exporting your HomeScreen. You'll have to create a separate file for it, like you did with DetailsScreen.
The HomeScreen.js file would look like:
class MyHomeScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: <LogoTitle />,
headerLeft: (
<Button
onPress={() => this.props.navigation.navigate('DrawerToggle')}
title={'Menu'}
/>
),
};
};
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
}
You NEED to add a render inside of your screens
Yes, I've read multiple threads on this topic but I still can't get my project to work: In My App.js I have a conditional rendering of a Login Screen or to render the Screen. This is also where my AppContainer lives. The method handleAddSubmit=()=>{} triggered by a button in my Header gives me the undefined error. Any suggestions?
App.js
return(
<View>
<AppNav/>
<Main
username={this.state.username}
onLoggedOut={this.LoggedOut}
/>
</View>
);
Main.js
import React from 'react';
import { StyleSheet, Text, View, Platform, ScrollView} from 'react-native';
import { Header} from 'react-native-elements';
import IntervalSelector from './HourCards/IntervalSelector'
import HourCard from './HourCards/HourCard'
export default class Main extends React.Component {
constructor(props){
super(props);
}
handleAddSubmit = () =>
{
this.props.navigation.navigate('ToggleNewHourFile');
}
render() {
const {username} = this.props;
return (
<View>
<Header
centerComponent={{ text: '[ ' + username + ' ]',
style: { color: '#fff',
fontFamily: Platform.OS === 'ios' ? 'AppleSDGothicNeo-UltraLight' : 'Roboto',
fontSize:20 } }}
backgroundColor='#18153f'
rightComponent=
{{
icon: 'home',
color: '#fff',
onPress: this.handleHomeSubmit,
underlayColor: 'transparent',
}}
leftComponent=
{{
icon: 'add',
color: '#fff',
onPress: this.handleAddSubmit,
underlayColor: 'transparent',
}}
statusBarProps={{barStyle:"light-content", translucent:'true'}}
/>
<IntervalSelector/>
<View>
<ScrollView>
<HourCard/>
</ScrollView>
</View>
</View>
);
}
}
StackNavigator.js
import {createStackNavigator, createAppContainer} from 'react-navigation';
import Main from '../main/Main';
import ToggleNewHourFile from '../main/HourCards/ToggleNewHourFile';
const StackNavigator = createStackNavigator(
{
Main: { screen: Main },
ToggleNewHourFile: {screen: ToggleNewHourFile},
}
);
const AppNav = createAppContainer(StackNavigator);
export default AppNav;
ToggleNewHourFile.js
import React from 'react';
import { StyleSheet, Text, View, Platform, TouchableOpacity, Image} from 'react-native';
class ToggleNewHourFile extends React.Component{
render(){
return(
<Text>Hallo</Text>
)
}
}
export default ToggleNewHourFile;
You reference Main in your stack navigator and also in your App.js. The Main in App.js does not have access to the stack navigator which is what's causing the error.
You can explicitly pass the navigator as a prop to Main, for example:
navigator;
...
<View>
<AppNav ref={nav => this.navigator = nav}/>
<Main
navigation={this.navigator}
username={this.state.username}
onLoggedOut={this.LoggedOut}
/>
or you can refactor your code to remove the Main component from App.js
I have a HomePage.js with only one button, Login Button, and when clicked I want to render LoginPage.js. I tried something like this but it's not working:
HomePage.js:
import React, { Component } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import LoginPage from './LoginPage';
class HomePage extends Component{
constructor () {
super();
this.state = {LoginPage:false};
}
showLogin = () => {
this.setState({LoginPage:true});
}
render() {
return(
<View>
<TouchableOpacity onPress={() => this.showLogin()}>
<Text>LogIn</Text>
</TouchableOpacity>
</View>
)
}
}
export default HomePage;
In React it's easily done with react-router but I don't know how to do it with React-Native.
EDIT 1 after including react-navigation I get the following error: Route 'navigationOptions' should declare a screen. Did I miss something?
App.js:
import React, {Component} from 'react';
import {StackNavigator} from 'react-navigation';
import HomePage from './HomePage';
import LoginPage from './LoginPage';
const App = StackNavigator({
Home: {screen: HomePage},
LoginPage: {screen: LoginPage},
navigationOptions: {
header: () => null,
}
});
export default App;
HomePage.js
import React, { Component } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import LoginPage from './LoginPage';
class HomePage extends Component{
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return(
<View>
<TouchableOpacity onPress={() => navigate('LoginPage'), { name: 'Login' }}>
<Text>Login</Text>
</TouchableOpacity>
<Button
onPress={() => navigate('LoginPage'), { name: 'Login' }}
>Log In</Button>
</View>
)
}
}
export default HomePage;
LoginPage.js
import React, { Component } from 'react';
import { Text, View } from 'react-native';
class LoginPage extends Component {
static navigationOptions = ({navigation}) => ({
title: navigation.state.params.name,
});
render() {
return (
<View>
<Text>This is login page</Text>
</View>
);
}
}
export default LoginPage;
Use React Navigation. Its the best and simple solution right now for react native.
for adding the library use npm install --save react-navigation
You can define class for routing like below using a StackNavigator.
import {
StackNavigator,
} from 'react-navigation';
const BasicApp = StackNavigator({
Main: {screen: MainScreen},
Profile: {screen: ProfileScreen},
, {
headerMode: 'none',
}
});
Then u can define classes like the one u mentioned in the question.
eg:
class MainScreen extends React.Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
const { navigate } = this.props.navigation;
return (
<Button
title="Go to Jane's profile"
onPress={() =>
navigate('Profile', { name: 'Jane' })
}
/>
);
}
}
and second class
class ProfileScreen extends React.Component {
static navigationOptions = ({navigation}) => ({
title: navigation.state.params.name,
});
render() {
const { goBack } = this.props.navigation;
return (
<Button
title="Go back"
onPress={() => goBack()}
/>
);
}
}
The navigate function can be used for navigating to a class and goBack can be used for going back to a screen.
For hiding header u can use header mode or specify navigation options in each screen
For details please refer : https://reactnavigation.org/
If you're just looking for a plugin like react-router to use on React Native, you can use react-native-router-flux, available here: https://github.com/aksonov/react-native-router-flux.
Also, it's better if you just put a reference to the function in there instead of invoking it. Something like:
<TouchableOpacity onPress={this.showLogin}>...</TouchableOpacity>