I'm new to react native, but I've been researching how to redirect to new activities with buttons for the last few hours with no avail. My current solution involves me using react-navigation from multiple files, with App.js creating the StackNavigator for the rest of the pages. However, whenever I press the button on Initial.js, nothing happens.
I followed Damien Manson's tutorial on Invariant Violation: The navigation prop is missing for this navigator, but the button still doesn't work. I tried referencing App before calling my button, but whenever I try to run it on the emulator, it doesn't show me an error log and it never loads. It stays at "Downloading JavaScript bundle: 100%" for minutes until the emulator itself crashes.
Here's my App.js
import Initial from './components/Initial'
import Statistics from './components/Statistics'
const RootStack = createStackNavigator({
Default: {
screen: Initial
},
Stats: {
screen: Statistics
}
});
const App = createAppContainer(RootStack);
export default App;
Here's my Initial.js
import { StyleSheet, ImageBackground, Image, View, Text, Button } from 'react-native';
import { Font } from 'expo';
import App from '../App';
import {withNavigation} from 'react-navigation';
import Statistics from './Statistics';
export default class Initial extends React.Component {
static navigationOptions = {header: null}
componentDidMount() {
Font.loadAsync({'pt': require('../assets/fonts/pt.ttf')});
Font.loadAsync({'Karla': require('../assets/fonts/Karla-Regular.ttf')});
Font.loadAsync({'Space-Mono': require('../assets/fonts/SpaceMono-Regular.ttf')});
}
state = { fontLoaded: false};
async componentDidMount() {
await Font.loadAsync({'pt': require('../assets/fonts/pt.ttf'),});
await Font.loadAsync({'Karla': require('../assets/fonts/Karla-Regular.ttf'),});
await Font.loadAsync({'Space-Mono': require('../assets/fonts/SpaceMono-Regular.ttf'),});
this.setState({fontLoaded: true});
}
render() {
return (
<ImageBackground
source = {require('../assets/blue-bin.jpg')}
style = {styles.container}>
<View style = {styles.parentView}>
{this.state.fontLoaded ? (<Text style={styles.logoText}>!arbitrary</Text>) : null}
<Image source = {require('../assets/sadparrot.gif')} style = {styles.logo}/>
<Text style = {styles.textBox}>With its easily navigatible interface, the Chicago-based app, !arbitrary, aims to educate the masses about recyclable items, while emphasizing the importance of being sustainable.</Text>
<View style = {styles.redirect}>
<Button
title="Start"
onPress={() => this.props.navigation.navigate('Statistics')}
/>
</View>
</View>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
height: '100%',
},
parentView: {
flex: 1,
flexDirection: 'column',
backgroundColor: 'rgba(5,9,12, 0.6)',
justifyContent: 'center',
alignItems: 'center'
},
logoText: {
color: '#fff',
fontSize: 36,
fontFamily: 'pt'
},
textBox: {
width: 200,
height: 175,
fontFamily: 'sans-serif',
fontSize: 14,
color: '#fff',
borderColor: '#fff',
borderWidth: 2,
justifyContent: 'center',
marginTop: 50,
padding: 20
},
logo: {
width: 200,
height: 200
},
redirect: {
width: 80,
height: 30,
marginTop: 30
},
statistics: {
flex: 1,
width: '100%',
height: '100%',
backgroundColor: '#1B384F'
},
bigText: {
color: '#fff',
fontSize: 20,
fontFamily: 'Space-Mono'
}
});
Here's my Statistics.js
import { StyleSheet, ImageBackground, Image, View, Text, Button } from 'react-native';
import { Font } from 'expo';
import {withNavigation} from 'react-navigation';
class Statistics extends React.Component {
render() {
return(
<Text>Avail!</Text>
);
}
}
export default withNavigation(Statistics);
NOTE: I omitted my StyleSheet in Initial.js for the sake of being concise.
You need to navigate to your screen name which is Stats.
<Button
title="Start"
onPress={() => this.props.navigation.navigate('Stats')}/>
I had the same issue. I fixed is by using withNavigation
In Statistics class,
1st, Import withNavigation.
import { withNavigation } from 'react-navigation';
Then, export Statistics class as below.
export default withNavigation(Statistics)
Try this
const RootStack = createStackNavigator({
Default: {
screen: Initial
},
Stats: {
screen: props => <Statistics {...props} />
}
});
Related
I am struggling a little bit. I have tried to create more components for my React native app but after I did it my ButtonSaving stoped redirecting to Dashboard for some reason. I was trying some ways to pass onRowPress to component but without luck. What do I do incorrectly here please?
Login button is working fine => redirecting to Dashboard
ButtonSaving not working at all => should redirect to Dashboard
AppNavigator.js
import { createStackNavigator } from 'react-navigation-stack'
import { createAppContainer } from 'react-navigation';
import Homepage from './components/Homepage/Homepage';
import Dashboard from './components/Dashboard/Dashboard';
const AppNavigator = createStackNavigator({
Homepage: Homepage,
Dashboard: { screen: Dashboard},
},
{
initialRouteName: 'Homepage',
defaultNavigationOptions: {
headerStyle: {
backgroundColor: 'white',
opacity: 70,
borderBottomColor: 'white',
borderColor: 'white'
},
headerTintColor: 'black',
headerTitleStyle: {
fontWeight: 'bold'
}
}
}
);
const Container = createAppContainer(AppNavigator);
export default Container;
Homepage.js
import React from 'react';
import { StyleSheet, Text, View, Button, Image } from 'react-native';
import {NavigationActions} from 'react-navigation';
// COMPONENTS
import ButtonSaving from './ButtonSaving/ButtonSaving';
class Homepage extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: false
},
this.handleClick = this.handleClick.bind(this);
this.onRowPress = this.onRowPress.bind(this);
}
handleClick() {
const counterApp = this.state.counter;
this.setState({
counter: counterApp + 1,
dashboard: 'Dashboard'
})
}
onRowPress = ({ navigation }) => {
this.props.navigation.navigate(this.state.dashboard);
}
render() {
return(
<View style={styles.container}>
{/* LOGIN BUTTON */}
<View style={styles.buttonContainer}>
<View style={styles.buttonLogin}>
<Button title="log in"
color="white"
onPress={() => this.props.navigation.navigate('Dashboard')}/>
</View>
</View>
{/* LOGO CONTAINER */}
<View style={styles.logoContainer}>
<Image
style={{height: 147, width: 170}}
source= {require('./REACT_NATIVE/AwesomeProject/logo.png')}
></Image>
</View>
{/* EXPLAINATION OF WALT */}
<Text style={styles.textContainer}>Lorem ipsum lorem upsum></Text>
{/* Needs to be refactored to VIEW */}
<ButtonSaving onPress={() => this.onRowPress}/>
</View>)
}
ButtonSaving.js
import React from 'react';
import { StyleSheet, Text, View, Button, Image, TouchableOpacity } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient';
class ButtonSaving extends React.Component {
constructor(props) {
super(props);
this.state = {
},
this.onRowPress = this.onRowPress.bind(this);
}
onRowPress = ({ navigation }) => {
this.props.navigation.navigate(this.state.dashboard);
}
render(){
return(
<View style={styleButton.container}>
<LinearGradient
colors={[
'#00b38f',
'#7dcf5a'
]}
style={styleButton.opacityContainer}>
<TouchableOpacity>
<Button
title="Start Saving"
color='white'
onPress={this.onRowPress}/>
</TouchableOpacity>
</LinearGradient>
</View>
)
}
}
const styleButton = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap',
width: '100%',
height: 50,
justifyContent: 'center',
marginTop: '39%'
},
opacityContainer: {
height: 48,
borderRadius: 25,
backgroundColor: 'darkgreen',
width: '70%',
justifyContent: 'center',
alignItems: 'center'
}
})
export default ButtonSaving;
You miss to put dashboard in your state in ButtonSaving.js
In Homepage.js when your are calling handleClick ?. Dunno how you got that working...
You say in the onRowPress this:
this.props.navigation.navigate(this.state.dashboard);
but I don't see anywhere that you set this.state.dashboard.
Probabbly you missed set it up.
It was simple refactor and this helped!
<ButtonSaving navigation ={this.props.navigation}/>
I will update solution for others later.
There is no point to save "dashboard" in the Homepage state or the ButtonSaving state.
In Homepage.js you don't need to pass onPress to ButtonSaving
...
<ButtonSaving navigation={this.props.navigation}/>
...
Next in ButtonSaving.js
onRowPress = () => {
this.props.navigation.navigate('Dashboard');
}
I have some issue when navigating from top Tabnavigator to other screens
so my app navigation is
My Orders Screen from Drawer => Top TabNavigatore (Accepted/Completed) => Order Details
In Route.js
I put every single navigation I want like Drawer - Auth navigation and so on, and I put a StackNavigator contain the Orders Screen like this:
const OrdersStack = createStackNavigator({
Orders: {
screen: Orders,
navigationOptions: ({ navigation }) => ({
headerLeft: (
// <TouchableOpacity onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}>
<TouchableOpacity
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
>
<Icon
name="ios-menu"
size={40}
style={{ margin: 10 }}
color="#2F98AE"
/>
</TouchableOpacity>
),
headerRight: <View />,
title: "My Orders",
headerTintColor: "#2F98AE",
headerStyle: {
borderBottomColor: "white"
},
headerTitleStyle: {
color: "#2F98AE",
// textAlign: "center",
flex: 1,
elevation: 0,
fontSize: 25
// justifyContent: "center"
}
})
}
});
In the Orders.js I put these:
import React, { Component } from "react";
import { createAppContainer, createStackNavigator } from "react-navigation";
import NavTabs from "../screens/NavTabs";
import NavOrderDetails from "../screens/NavOrderDetails";
// create a component
export default class Orders extends Component {
render() {
return <MyOrdersScreen />;
}
}
export const root = createStackNavigator({
NavTabs: NavTabs,
NavOrderDetails: NavOrderDetails
});
const MyOrdersScreen = createAppContainer(root);
As I mentioned in Orders.js it Contains Tabs and Order Details
In Tabs, I'm creating a createMaterialTopTabNavigator
import { createMaterialTopTabNavigator } from "react-navigation";
import AcceptedOrders from "../screens/AcceptedOrders";
import CompletedOrders from "../screens/CompletedOrders";
const MainTabs = createMaterialTopTabNavigator(
{
Accepted: { screen: AcceptedOrders },
Completed: { screen: CompletedOrders }
},
{
tabBarOptions: {
activeTintColor: "#fff",
inactiveTintColor: "#ddd",
tabStyle: {
justifyContent: "center"
},
indicatorStyle: {
backgroundColor: "#fcc11e"
},
style: {
backgroundColor: "#2F98AE"
}
}
}
);
export default MainTabs;
and another screen is OrderDeatils.js
import { createStackNavigator } from "react-navigation";
import OrderDetails from "../screens/OrderDetails";
import React, { Component } from "react";
import { View } from "react-native";
const OrderDetailsStack = createStackNavigator({
OrderDetails: {
screen: OrderDetails,
navigationOptions: () => ({
title: "Order Details",
headerRight: <View />,
headerTintColor: "#2F98AE",
headerStyle: {
borderBottomColor: "white"
},
headerTitleStyle: {
color: "#2F98AE",
flex: 1,
elevation: 0,
fontSize: 25
}
})
}
});
export default OrderDetailsStack;
Here are a screenShots it should explain what I mean
1- My Orders
2- Order Details
If i understand, you are concerned about the blank header that appears on top of the screen under your first header.
That one is created by createStackNavigator.
A the first Stack that creates the first Header named OrdersStack.
Inside that you have the root constant (probably, as there isn't the full code) that is creating the second header.
Inside root you are then defining your createMaterialTopTabNavigator with your two screens, that's showing the topBar with the label "accepted" and "completed".
To hide that white space you have to disable your root header doing:
export const root = createStackNavigator({
NavTabs: NavTabs,
NavOrderDetails: NavOrderDetails
},
{
defaultNavigationOptions:{
header:null
}
});
UPDATE.
You have two ways to fix this and still have a backButton:
1) You can either create a parent CustomHeader that, using react-navigation's withNavigation HOC, is aware about his childrens navigation state.
2) Dinamically hide the parent header when the second one is showing. You can accomplish this using this.props.navigation.dangerouslyGetParent().dangerouslyGetParent().setParams({showHeader:false})
then your OrdersStack would be:
const OrdersStack = createStackNavigator({
Orders: {
screen: Orders,
navigationOptions: ({ navigation }) => {
var defaultHeader={
headerLeft: (
<TouchableOpacity
onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
>
<Icon
name="ios-menu"
size={40}
style={{ margin: 10 }}
color="#2F98AE"
/>
</TouchableOpacity>
),
headerRight: <View />,
title: "My Orders",
headerTintColor: "#2F98AE",
headerStyle: {
borderBottomColor: "white"
},
headerTitleStyle: {
color: "#2F98AE",
// textAlign: "center",
flex: 1,
elevation: 0,
fontSize: 25
// justifyContent: "center"
}
}
if (navigation.state.params)
return(navigation.state.params.showHeader?{defaultHeader}:null)
else return defaultHeader
}
}
});
Essentially, I have a Cloud Icon in the upper right hand corner of my app screen that when I click it I want it to edit an AsyncStorage variable in my application. And I am trying to get that variable to change the style of the <Icon> class.
To do this, I am trying to make it be a clickable element but I don't know how to transfer this onClick to a variable that each screen file sees.
Right now, the icon style ie based on navigation.state.params.cloud but that is only established on ComponentDidMount() in the each screen file.
Does anyone have any suggestions on how to accomplish this?
My React-Native app is set up like so:
index.js
import { AppRegistry } from 'react-native';
import App from './app/config/app';
AppRegistry.registerComponent('MobApp', () => App);
/config/app.js
import React from 'react';
import { TouchableOpacity, ActivityIndicator, AsyncStorage, Button, StatusBar, StyleSheet, View, Text, Image } from 'react-native';
import { StackNavigator, SwitchNavigator } from 'react-navigation'; // Version can be specified in package.json
import { configureFontAwesomePro } from "react-native-fontawesome-pro";
import Icon from "react-native-fontawesome-pro";
configureFontAwesomePro();
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
import AuthLoadingScreen from '../screens/AuthLoadingScreen';
import MainScreen from '../screens/main';
import SettingsScreen from '../screens/settings';
const cloudLogo = async function() {
let cloud = await AsyncStorage.getItem('cloud');
cloud = cloud === 'true';
cloud = !cloud;
await AsyncStorage.setItem('cloud', cloud.toString())
return cloud;
}
var navo = ({ navigation }) => {
return {
headerStyle: { backgroundColor:"#fff"},
headerTitle: (<View style={{flex: 1, flexDirection: 'row', justifyContent: 'center' }}><Image style={{ width:25, height: 25, marginTop: 3, marginRight: 5}} source={require('../images/logo.png')}/><Text style={{ color:"#3F61E7", fontSize: 26, fontWeight:"600" }}>MyCompany</Text></View>),
headerRight: (<TouchableOpacity activeOpacity={0.8} onPress={ async () => {
let cloud = await AsyncStorage.getItem('cloud');
cloud = cloud === 'true';
cloud = !cloud;
await AsyncStorage.setItem('cloud', cloud.toString())
console.log((await syncUtil.getLocal()).cloud)
} } style={{ marginRight:15 }}>
<Icon name="cloud" type={ typeof navigation.state.params == 'undefined' ? "solid" : (navigation.state.params.cloud ? "solid" : "light") } color="#3F61E7" size={30} />
</TouchableOpacity>)
}
}
const AppStack = StackNavigator({
Home: { screen: MainScreen, navigationOptions: navo },
Settings: { screen: SettingsScreen, navigationOptions: navo },
});
export default SwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack
},
{
initialRouteName: 'AuthLoading',
}
);
I'm trying to create a React Native app with some basic routing.
This is my code so far:
App.js:
import React from 'react'
import { StackNavigator } from 'react-navigation'
import MainScreen from './classes/MainScreen'
const AppNavigator = StackNavigator(
{
Index: {
screen: MainScreen,
},
},
{
initialRouteName: 'Index',
headerMode: 'none'
}
);
export default () => <AppNavigator />
MainScreen.js:
import React, { Component } from 'react'
import { StyleSheet, Text, View, Button, TouchableOpacity, Image } from 'react-native'
import HomeButton from './HomeButton'
export default class MainScreen extends Component {
static navigatorOptions = {
title: 'MyApp'
}
constructor(props) {
super(props)
}
render() {
return (
<View style={styles.container}>
<Image source={require('../img/logo.png')} style={{marginBottom: 20}} />
<HomeButton text='Try me out' classType='first' />
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
})
HomeButton.js:
import React, { Component } from 'react'
import { StyleSheet, Text, View, Button, TouchableOpacity } from 'react-native'
import { StackNavigator } from 'react-navigation'
export default class HomeButton extends Component {
constructor(props) {
super(props)
}
render() {
return (
<TouchableOpacity
onPress={() => navigate('Home')}
style={[baseStyle.buttons, styles[this.props.classType].style]}
>
<Text style={baseStyle.buttonsText}>{this.props.text.toUpperCase()}</Text>
</TouchableOpacity>
)
}
}
var Dimensions = require('Dimensions')
var windowWidth = Dimensions.get('window').width;
const baseStyle = StyleSheet.create({
buttons: {
backgroundColor: '#ccc',
borderRadius: 2,
width: windowWidth * 0.8,
height: 50,
shadowOffset: {width: 0, height: 2 },
shadowOpacity: 0.26,
shadowRadius: 5,
shadowColor: '#000000',
marginTop: 5,
marginBottom: 5
},
buttonsText: {
fontSize: 20,
lineHeight: 50,
textAlign: 'center',
color: '#fff'
}
})
const styles = {
first: StyleSheet.create({
style: { backgroundColor: '#4caf50' }
})
}
Everything works fine, but when pressing the button I get
Can't find variable: navigate
I've read that I have to declare it like this:
const { navigate } = this.props.navigation
So I edited HomeButton.js and added that line at the beginning of the render function:
render() {
const { navigate } = this.props.navigation
return (
<TouchableOpacity
onPress={() => navigate('Home')}
style={[baseStyle.buttons, styles[this.props.classType].style]}
>
<Text style={baseStyle.buttonsText}>{this.props.text.toUpperCase()}</Text>
</TouchableOpacity>
)
}
Now I get:
TypeError: undefined is not an object (evaluating 'this.props.navigation.navigate')
It seems that the navigation object is not coming into the properties, but I don't understand where should I get it from.
What am I missing here?
React-navigation pass navigation prop to the screen components defined in the stack navigator.
So in your case, MainScreen can access this.props.navigation but HomeButton can't.
It should work if you pass navigation prop from MainScreen to HomeButton :
<HomeButton text='Try me out' classType='first' navigation={this.props.navigation}/>
Edit: You have to define the Homescreen in your stack navigator in order to navigate to it, your onPress={() => navigate('Home')} won't work until then.
I'm implementing React Navigation in my React Native app, and I'm wanting to change the background and foreground colors of the header. I have the following:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import { StackNavigator } from 'react-navigation';
export default class ReactNativePlayground extends Component {
static navigationOptions = {
title: 'Welcome',
};
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.android.js
</Text>
<Text style={styles.instructions}>
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
const SimpleApp = StackNavigator({
Home: { screen: ReactNativePlayground }
});
AppRegistry.registerComponent('ReactNativePlayground', () => SimpleApp);
By default the background color of the heading is white, with a black foreground. I've also looked at the documentation for React Navigation but I'm not able to find where it shows how to set the styling. Any help?
In newer versions of React Navigation you have a flatter settings object, like below:
static navigationOptions = {
title: 'Chat',
headerStyle: { backgroundColor: 'red' },
headerTitleStyle: { color: 'green' },
}
Deprecated answer:
Per the docs, here, you modify the navigationOptions object. Try something like:
static navigationOptions = {
title: 'Welcome',
header: {
style: {{ backgroundColor: 'red' }},
titleStyle: {{ color: 'green' }},
}
}
Please don't actually end up using those colors though!
According to documentation you can use "navigationOptions" style like this.
static navigationOptions = {
title: 'Chat',
headerStyle:{ backgroundColor: '#FFF'},
headerTitleStyle:{ color: 'green'},
}
For more info about navigationOptions you can also read from docs:-
https://reactnavigation.org/docs/navigators/stack#Screen-Navigation-Options
Try this working code
static navigationOptions = {
title: 'Home',
headerTintColor: '#ffffff',
headerStyle: {
backgroundColor: '#2F95D6',
borderBottomColor: '#ffffff',
borderBottomWidth: 3,
},
headerTitleStyle: {
fontSize: 18,
},
};
Notice! navigationOptions is differences between Stack Navigation and Drawer Navigation
Stack Navigation Solved.
But for Drawer Navigation you Can add Your own Header and Make Your Styles with contentComponent Config:
First import { DrawerItems, DrawerNavigation } from 'react-navigation' Then
Header Before DrawerItems:
contentComponent: props => <ScrollView><Text>Your Own Header Area Before</Text><DrawerItems {...props} /></ScrollView> .
Footer After DrawerItems:
contentComponent: props => <ScrollView><DrawerItems {...props} /><Text>Your Own Footer Area After</Text></ScrollView> .
Try this code:
static navigationOptions = {
headerTitle: 'SignIn',
headerTintColor: '#F44336'
};
good luck!