I'm attempting to use a React Navigation StackNavigator for a process that includes a static component at the top of the page and varying components at the bottom. The code I'm using is:
const Navigator = StackNavigator ({
splash: Splash,
prompt: Prompt,
pinCheck: PinCheck
}, {
initialRouteName: 'splash'
});
export default class Login extends React.Component
{
constructor (props)
{
super (props);
// code to set up animation state
}
componentDidMount()
{
// code to set up animation
}
finish_ (state)
{
this.props.navigation.navigate ('main', state);
}
render()
{
const screen = Dimensions.get('screen');
return (
<KeyboardAvoidingView style={Global.styles.verticalFill} ref={this.saveContainerRef}>
<ScrollView style={{flex: 1}} contentContainerStyle={{justifyContent: 'space-between'}}>
<Animated.View style={{opacity:this.state.fade1,alignItems:'center'}} >
<Image
style={{width:screen.width * 0.6,height: screen.height*0.55}}
source={imgLogo}
resizeMode='contain'
/>
<Navigator />
</Animated.View>
</ScrollView>
</KeyboardAvoidingView>
);
}
}
When I run this, however, my initial route component is not shown. It works correctly if I swap <Navigator/> to <Splash/> however, so the component itself definitely works in this context.
Any ideas what's wrong?
There is a problem in your navigation setup.
All the routes in the StackNavigator must declare a screen
as mentioned in the docs
const Navigator = StackNavigator ({
splash: {
screen: splash
},
prompt: {
screen: prompt
},
pinCheck: {
screen: pinCheck
}
}, {
initialRouteName: 'splash'
})
Related
I am building an app that displays jobs and the commute time for each of them. I have a JobsComponent, where I display the jobs. In this component, I have added an icon on the navbar. When the icon is tapped, a MapComponent should be opened. In this map, I want to display a pin for each job.
The problem that I'm facing is that I've defined the icon in my AppNavigator.js and I want to have the onPress() functionality in JobsComponent.js, but I don't know how to do this. What I've tried:
Adding an ({ onPress }) param to navHeaderRight:
export const navHeaderRight = ({ onPress }) =>
(/UI component goes here/
)
but with no results.
One other idea I had was to define the onPress() behaviour in AppNavigator.js, but this means importing a lot of stuff (array of jobs, details for each job) to AppNavigator.js, which is not a good design decision from my point of view.
I tried just doing a console.log in navHeaderRight.onPress from JobsComponent to see if it works at all. It doesn't.
This is my AppNavigator.js:
import {TouchableHighlight, Image, View} from 'react-native';
import MapComponent from './MapComponent';
export const navHeaderRight = ({ onPress }) =>
(
<View style={{marginRight: 5}}>
<TouchableHighlight underlayColor="transparent">
<Image
source={require('../assets/map.png')}
style={{height: 40, width: 40}}/>
</TouchableHighlight>
</View>
)
const Navigator = createStackNavigator({
Jobs: {
screen: Jobs,
navigationOptions: {
headerRight: navHeaderRight
}
},
MapComponent: {
screen: MapComponent,
navigationOptions: {
header: null
}
}
//other screens defined in the navigator go here
});
const AppNavigator = createAppContainer(Navigator);
export default AppNavigator;
And this is my JobsComponent.js. Here, I try to define the onPress() behaviour in componentDidMount().
import {navHeaderRight} from './AppNavigator';
class Jobs extends Component {
componentDidMount() {
navHeaderRight.onPress = () => {
this.props.navigation.navigate('MapComponent', {/*other params go here*/})
}
}
}
Expected result: when navHeaderRight.onPress is called, the MapComponent should be opened.
**
Actual result: Nothing happens.
**
Any help will be greatly appreciated. :)
You can use React Navigation route parameters to achieve this.
In AppNavigator.js:
import { TouchableHighlight, Image, View } from 'react-native';
import MapComponent from './MapComponent';
const navHeaderRight = (navigation) => {
const handlePress = navigation.getParam('handlePress', null);
return (
<View style={{marginRight: 5}}>
<TouchableHighlight
underlayColor="transparent"
onPress={handlePress}
>
<Image
source={require('../assets/map.png')}
style={{height: 40, width: 40}}
/>
</TouchableHighlight>
</View>
);
}
const Navigator = createStackNavigator({
Jobs: {
screen: Jobs,
navigationOptions: ({ navigation }) => ({
headerRight: navHeaderRight(navigation),
}),
},
MapComponent: {
screen: MapComponent,
navigationOptions: {
header: null,
}
}
});
const AppNavigator = createAppContainer(Navigator);
export default AppNavigator;
In JobsComponent.js:
class Jobs extends Component {
componentDidMount() {
const handlePress = () => console.log('whatever function you want');
this.props.navigation.setParams({ handlePress });
}
// [...]
}
I'm building my first react-native application, and I encountered a problem I could not solve.
on the home screen, I don't want any header, and on the other pages, I would like to keep the navigation option.
I want to build a simple home page, with I buttons sign-up and login, the problem is I can't hide the first header on any of the app pages, i dont know if the problem is in the header, or maybe in some other component?
please review the code below.
class HomeScreen extends React.Component {
static navigationOptions = {
header:null,
headerVisible:false
}
render() {
return (
<ImageBackground source = {backgroundImage} style = {styles.backgroundImage}>
<View style = {styles.container}>
<View style = {styles.btn_login}>
<Button title = "SIGNUP NOW" color='#69428F' onPress ={this._showSignUpPage}/>
</View>
<View style = {styles.btn_signup}>
<Button title = "LOGIN" color='#AAA3A3' onPress = {this._showLoginPage}/>
</View>
</View>
</ImageBackground>
);
}
class SignupScreen extends React.Component{
// this hides the navigation, and i cannot see the navigation
// static navigationOptions = {
// header:null,
// }
render(){
return (
<View style={styles.container_Signup}>
<Button title="SignupScreen"/>
<StatusBar barStyle="default" />
</View>
);
}
}
const AppStack = createStackNavigator(
{
Home:{
screen:HomeScreen,
navigationOptions:{
header:null
}
},
SIGNUP:SignupScreen,
LOGIN:LoginScreen
},
{
navigationOptions :{
header:null
}});
as you can see, I tried placing the header:null inside the class, and inside the stackNavigator, but nothing seems to work.
Try to set header mode none as follow:
const AppStack = createStackNavigator( {
Home:{
screen: HomeScreen,
navigationOptions: {
header: null
}
},
SIGNUP: SignupScreen,
LOGIN: LoginScreen
}, {
headerMode: "none"
});
I hope it help you.
I use TabNavigator and DrawerNavigator both in my app.
When I open drawer with 'slide' option, contents slide with drawer but TabBar doesn't slide together.
I want to make TabBar slide together but I cannot find any option about it.
How Can I do this?
Could you help?
-------------[code]----------------
1) app.js
...skip ...
const DailySalesStack = createStackNavigator({
DailySalesMain: DailySalesMain,
},
{
defaultNavigationOptions:{
header:null
},
initialRouteName:'DailySalesMain'
});
const DailyRivalRankStack = createStackNavigator({
DailyRivalRankMain: DailyRivalRankMain,
},
{
defaultNavigationOptions:{
header:null
},
initialRouteName:'DailyRivalRankMain'
});
const SalesAnalysisStack = createStackNavigator({
SalesAnalysisMain: SalesAnalysisMain,
},
{
defaultNavigationOptions:{
header:null
},
initialRouteName:'SalesAnalysisMain'
});
const DailySalesAnalysisStack = createStackNavigator({
DailySalesAnalysisMain: DailySalesAnalysisMain,
},
{
defaultNavigationOptions:{
header:null
},
initialRouteName:'DailySalesAnalysisMain'
});
/// DRAWER!
const SalesStack = createDrawerNavigator({
DailySales: {
screen: DailySalesStack,
},
DailyRivalRank: {
screen: DailyRivalRankStack,
},
SalesAnalysis: {
screen: SalesAnalysisStack,
},
DailySalesAnalysis: {
screen: DailySalesAnalysisStack,
},
},
{
contentComponent:SalesSlideMenu,
drawerType: 'slide',
drawerWidth:230*REM,
}
);
...skip...
/// BOTTOM TAB
export default createAppContainer(createBottomTabNavigator(
{
MainStack:MainStack,
ApprovalStack:ApprovalStack,
SalesStack:SalesStack,
OrganizationStack:OrganizationStack,
SettingStack:SettingStack,
},
{
tabBarComponent: TabBar,
}
));
2) tabBar.js
const TAB_LIST = [
require('../../resources/images/tabIcon_main.png'),
require('../../resources/images/tabIcon_approval.png'),
require('../../resources/images/tabIcon_sales.png'),
require('../../resources/images/tabIcon_organization.png'),
require('../../resources/images/tabIcon_settings.png'),
];
export default class TabBar extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log("[TabBar.js] : Render ====");
const {
onTabPress,
navigation
} = this.props;
const { routes, index: activeRouteIndex } = navigation.state;
return (
<SafeAreaView style={{backgroundColor:'rgb(250,250,250)'}}>
<View style={styles.rootContainer}>
{routes.map((route, routeIndex) => {
const isRouteActive = routeIndex === activeRouteIndex;
return (
<TouchableWithoutFeedback key={routeIndex} onPress={() => {onTabPress({ route });}}>
<View style={styles.tabIconContainer}>
<Image style={[styles.icon,isRouteActive&&{tintColor:'black'}]} source={TAB_LIST[routeIndex]} resizeMode={'contain'}/>
{/* <View style={[styles.badge]}><Text style={[styles.text,styles.badgeNumber]}>12</Text></View> */}
</View>
</TouchableWithoutFeedback>
);
})}
</View>
</SafeAreaView>
);
}
}
3) SlideMenu.js
const MENU_LIST = [
{'icon':require('../../resources/images/dailySales.png'),'label':'Daily Sales','subLabel':'','route':'DailySales'},
{'icon':require('../../resources/images/rivalRank.png'),'label':'Daily Rival Rank','subLabel':'','route':'DailyRivalRank'},
{'icon':require('../../resources/images/salesAnalysis.png'),'label':'Sales Analysis','subLabel':'','route':'SalesAnalysis'},
{'icon':require('../../resources/images/dailySalesAnalysis.png'),'label':'Daily Sales Analysis','subLabel':'','route':'DailySalesAnalysis'},
]
class SlideMenuTab extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<View style={{flex:0}}>
<TouchableWithoutFeedback onPress={()=>this.props.navigation.navigate(this.props.data.route+"Main")}>
<View style={[styles.tabContainer,this.props.focused&&{backgroundColor:'rgb(247,247,247)'}]}>
<Image style={styles.tabIcon} source={this.props.data.icon} resizeMode={'contain'}/>
<View style={styles.labelContainer}>
<Text style={[styles.text,styles.label]}>{this.props.data.label}</Text>
<Text style={[styles.text,styles.subLabel]}>{this.props.data.subLabel}</Text>
</View>
</View>
</TouchableWithoutFeedback>
</View>
)
}
}
export default class SalesSideMenu extends React.Component {
constructor(props) {
super(props);
console.log("[SalesSideMenu.js] : Constructor");
}
render() {
console.log("[SalesSideMenu.js] : render ====");
let menuList = [];
MENU_LIST.forEach((data)=>{
let focused = this.props.activeItemKey === data.route;
menuList.push(
<SlideMenuTab data={data} focused={focused} navigation={this.props.navigation}/>
);
})
return (
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
<View style={styles.rootContainer}>
{menuList}
</View>
</SafeAreaView>
);
}
}
4) Screen with drawer
export default class DailySalesMain extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log("[DailySalesMain.js] : render ====");
return (
<View style={{flex:1,backgroundColor:'white',alignItems:'center',justifyContent:'center'}}>
<TouchableWithoutFeedback onPress={()=>this.props.navigation.openDrawer()}>
<View style={{width:'100%',height:50}}>
<View style={{width:50,height:50,backgroundColor:'blue'}}></View>
</View>
</TouchableWithoutFeedback>
<Text>DailySalesMain</Text>
</View>
);
}
}
If youre ussing React Native... This should be the default behaviour at least in iOS. Android Material Design supports the Drawer as a design desition, so if you want to hide the TabBar, just make a custom one or include the TabBar inside the Drawer.
But warning, hidding the TabBar may sometimes bring errors in Apple Store when you upload your app for revision.
From the TabBar section in the Human Interface Guidelines of iOS....
Don't hide a tab bar when people navigate to different areas in your app. A tab bar enables global navigation for your app, so it should remain visible everywhere. The exception to this is in modal views. Because a modal view gives people a separate experience that they dismiss when they're finished, it's not part of the overall navigation of your app.
Use badging to communicate unobtrusively. You can display a badge—a red oval containing white text and either a number or an exclamation point—on a tab to indicate that new information is associated with that view or mode.
In other words.. if you plan to support iOS in your apps, you should try to always show the TabBar somehow, or the store might give you some usability complains like this one.
We noticed that several screens of your app were crowded or laid out in a way that made it difficult to use your app.
Please see attached screenshot for reference.
Next Steps
To resolve this issue, please revise your app to ensure that the content and controls on the screen are easy to read and interact with.
I'm trying to make an application in react native that has a tab navigation screen as the main screen and a button that goes to another screen. I'm having a problem in making everything looking correct with the status bar.
I have this as my navigator:
const tabConfiguration = {
DecksList: {
screen: DecksList,
navigationOptions: {
title: 'Deck List'
}
},
NewDeck: {
screen: NewDeck,
navigationOptions: {
title: 'New Deck'
}
},
};
const TabNavigator =
Platform.select({
ios: createBottomTabNavigator(tabConfiguration),
android: createMaterialTopTabNavigator(tabConfiguration)
});
const StackNavigator = createStackNavigator({
Main: {
screen: TabNavigator,
navigationOptions: {
header: null
}
},
Deck: {
screen: Deck,
navigationOptions: {
title: 'Deck'
}
}
});
export default createAppContainer(StackNavigator);
And my App.js is like this:
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<StatusBarView backgroundColor={'red'}
barStyle="light-content"/>
<AppNavigator/>
</View>
);
}
}
The StatusBarView is:
const StatusBarView = props => {
const {backgroundColor} = props;
return (
<View style={{backgroundColor, height: Constants.statusBarHeight}}>
<StatusBar translucent backgroundColor={backgroundColor} {...props} />
</View>
);
};
With the code like that, my tab screen like this:
And my other screen end like this:
This second screen has a header higher than it should because of the StatusBarView. If I remove it, the second screen looks OK:
But the tabs screen goes behind the status bar:
Does anyone know how can properly handle this? I need the status bar for one screen (the tabs one) and can't have it for the other screen (the one without tabs).
I made a snack here: https://snack.expo.io/#esscheffer/flashcards-start
This line will solve the issue
navigationOptions: { headerForceInset: {top: 'never'}, ... }
On first screen I have a component having some listing of products say ProductListing.js. And on drawer navigator I have some check boxes. I want to set state of ProductListing.js when I am clicking on any check box of navigator.
App.js
import React, {Component} from 'react';
import Router from './src/config/routes';
export default class App extends React.Component {
render () {
return (
<Router/>
);
}
}
Router.js
export default DrawerNavigator({
Dashboard: {screen: Dashboard},
CreateLogin: {screen: CreateLogin},
Test: {screen: Test}
}, {
contentComponent: SideMenu,
drawerWidth: 300,
drawerPosition: 'right'
});
SideMenu.js
render () {
const { data, searchTerm, searchAttribute, ignoreCase, checked } = this.state;
return (
<View style={styles.container}>
<ScrollView>
<View>
<TextInput
style={styles.search} placeholder={"Search"}
onChangeText={searchTerm => this.setState({ searchTerm })} />
<SearchableFlatList
data={data} searchTerm={searchTerm}
searchAttribute={searchAttribute} ignoreCase={ignoreCase}
renderItem={({ item, index }) => <CheckBox
title={item.name +' ('+ item.count+')'}
onPress={() => this.handleChange(item.id)}
checked={checked[item.id]} />
}
keyExtractor={({id}, index) => index.toString()} />
</View>
</ScrollView>
<View style={styles.footerContainer}>
<Text>Apply filter by choosing filter values</Text>
</View>
</View>
);
}
}
ProductListing.js
constructor(props){
super(props);
this.state ={ isLoading: true,isloadmore: false, page :1,dataSource: [],countryFilter:0, gradesFilter:'',country:'',totalRecord:0};
}
render(){
const { navigation } = this.props;
return(
<View style={{flexDirection:'column',paddingRight:8}}>
<Button
title='Filter'
buttonStyle={{backgroundColor:'#000000',borderRadius:2}}
onPress={() => navigation.dispatch(DrawerActions.openDrawer())}
/>
</View>
);
}
Now on click of handleChange in SideMenu.js I want to update state of gradesFilter in ProductListing.js where I am updating my product listing.
You can easily achieve this with a portal! Take a look here.
You can pass the parameters from drawer to the Product screen via navigation params.
For this, you need to stackNavigator inside your DrawerNavigator like:
Router:
const MyStack = StackNavigator({
Dashboard: {screen: Dashboard},
CreateLogin: {screen: CreateLogin},
Test: {screen: Test}
});
const MyDrawer = DrawerNavigator({
Main: { screen: MyStack }
},
{
contentComponent: SideMenu,
drawerWidth: 300,
drawerPosition: 'right'
});
export const Router = StackNavigator({
Login: {screen: Login},
Drawer: {
screen: MyDrawer,
navigationOptions: { header: null } } //prevent double header
});
Now you can navigate and pass parameter from login or Dashboard or anyother screen via Drawer like
_login() {
this.props.navigation.navigate('Drawer', { parameter: userName });
}
And to use this parameter you need to access:
const { parameter } = this.props.navigation.state.params;
which you can further use to set state like
this.setState({
productSelection: parameter)};
Another approach can be via, listener/ Callback handler.