For a school assignment we're creating an app in react native with react navigation and redux. Because all of us are new to react we have an issue we are unable to resolve.
We want to change the title of the header when a certain button is clicked. The first time we click a button it changes the header title just fine. The problem arrises when we press a different button, the header doesn't change. Note that no matter what option we choose, we always go to the same screen.
import React from 'react';
import { createStackNavigator, createAppContainer, createDrawerNavigator } from 'react-navigation';
import {connect} from 'react-redux';
import { store } from '#redux/MyStore';
import { Ionicons } from '#expo/vector-icons';
import ScannerScreen from '#screens/ContactScreen';
import EventsScreen from '#screens/ListScreen';
const ContactStack = createStackNavigator({
Contact: {
screen: ContactScreen,
navigationOptions: ({navigation}) => ({
headerStyle: {backgroundColor: '#fa8231'},
headerTitleStyle: {fontSize: 18},
title: store.getState().setupState.title,
headerLeft: <Ionicons
name="md-menu" style={{marginLeft:10}}
size={28}
onPress={() => navigation.toggleDrawer()} /> //menu button
})
}
});
// Code to create stack for the ListStack
const DrawerStack = createDrawerNavigator({
Contact: ContactStack,
List: ListStack
});
const PrimaryNavigation = createStackNavigator({
ListStack: {
screen: ListStack,
navigationOptions: {
header: null,
},
},
DrawerStack: {
screen: DrawerStack,
navigationOptions: {
header: null,
},
},
},
{
initialRouteName: 'ListStack',
});
const AppContainer = createAppContainer(PrimaryNavigation);
class AppNavigation extends React.Component {
render() {
return <AppContainer/>
}
}
export default (AppNavigation)
We did get it working when we put the title bar in the DrawerNavigator, but since we want the Drawer in from of the header that is not an option. My speculation is that the stack is created once with a certain title and never gets updated when switching screens using the DrawerNavigator but we have no clue how to fix that.
Thanks in advance!
From what I understand you need to change the title when a screen is loaded in stack.So you could use some like:
class ScreenInContactStack extends React.Component{
//Constryctor
static navigationOptions = ({navigation}) => ({
title: (navigation.state.params || {}).title || 'Chat! ',
});
//Remaining Logic
}
and while calling
this.props.navigation.navigate('ScreenInContactStack', {title: yourTitle + ' ',});
Don't know why but the Appbar condense the title to like yourTi.. to avoid this add a space to the title.
Try this:
map title as a prop to force ContactStack to re-render whenever it changes
class ContactStack extends React.Component {
render() {
const { title } = this.props.setupState;
const Stack = createStackNavigator({
Contact: {
screen: ContactScreen,
navigationOptions: ({navigation}) => ({
headerStyle: {backgroundColor: '#fa8231'},
headerTitleStyle: {fontSize: 18},
title,
headerLeft: <Ionicons
name="md-menu" style={{marginLeft:10}}
size={28}
onPress={() => navigation.toggleDrawer()} /> //menu button
})
}
});
return <Stack />;
}
}
const mapStateToProps = ({ setupState }) => ({setupState});
export default connect(mapStateToProps)(ContactStack);
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 want to display HeaderLeft Menu icon globally in all screens. When I click on Menu Icon, I need to display Drawer Menu. I use "OpenDrawer", "CloseDrawer" methods for open/close drawer menu.
But I am always getting "undefined is not a function (evaluating 'props.navigation.openDrawer()')". I also tried the following
props.navigation.dispatch(DrawerActions.openDrawer())
props.navigation.navigate(openDrawer())
But None of the above worked. Here is my partial code
const MenuButton = (props) => {
return (
<View>
<TouchableOpacity onPress={() => { props.navigation.dispatch(DrawerActions.openDrawer())} }>
<Text>Menu</Text>
</TouchableOpacity>
</View>
)
};
const MyDrawerNavigator = createDrawerNavigator(
{
Wishist: {
screen: wishlist
},
},
{
contentComponent: SideMenuScreen,
drawerWidth: 200,
}
);
const AppNavigator = createBottomTabNavigator({
Home: {
screen: createStackNavigator({
Home: {
screen: Home
},
Contact: {
screen: Contact
}
},
{
defaultNavigationOptions: ({ navigation }) => ({
headerStyle: {
backgroundColor: 'white',
borderWidth:0,
borderBottomWidth:0
},
headerTitle: headerTitleNavigationOptions('HOME'),
headerLeft: <MenuButton navigation={navigation}/>,
headerRight: headerRightNavigatorOptions(navigation)
})
}),
navigationOptions: ({ navigation }) => ({
headerStyle: {
backgroundColor: 'white',
borderWidth:0,
borderBottomWidth:0
},
}),
}},
{
tabBarOptions: {
showLabel: false,
style: {
backgroundColor: 'white',
borderTopWidth:1
}
},
initialRouteName: 'Home',
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
animationEnabled: false,
swipeEnabled: false
}
);
const App = createAppContainer(AppNavigator);
you need to import the DrawerActions in your component from react-navigation-drawer as explained in the docs
DrawerActions is an object containing methods for generating actions specific to drawer-based navigators. Its methods expand upon the actions available in NavigationActions.
The following actions are supported:
openDrawer - open the drawer
closeDrawer - close the drawer
toggleDrawer - toggle the state, ie. switche from closed to open and
vice versa
import { DrawerActions } from 'react-navigation-drawer';
this.props.navigation.dispatch(DrawerActions.openDrawer())
The react-navigation api do not provide more information, but you can consult the NavigationActions api reference for more information.
NavigationActions reference
All NavigationActions return an object that can be sent to the router using navigation.dispatch() method.
Note that if you want to dispatch react-navigation actions you should use the action creators provided in this library.
You need to import NavigationActions in your component and then you can dispatch your action with something like this.props.navigation.dispatch(navigateAction);
import { NavigationActions } from 'react-navigation';
const navigateAction = NavigationActions.navigate({
routeName: 'Profile',
params: {},
action: NavigationActions.navigate({ routeName: 'SubProfileRoute' }),
});
this.props.navigation.dispatch(navigateAction);
also as explained from Ashwin Mothilal, make sure you are passing the navigation prop inside your component. For example you can run a console.warn(props) and immediatly see the result on the emulator.
This is your component:
import { DrawerActions } from 'react-navigation-drawer';
const MenuButton = (props) => {
return (
<View>
<TouchableOpacity onPress={() => {
console.warn(props);
props.navigation.dispatch(DrawerActions.openDrawer());
}}>
<Text>Menu</Text>
</TouchableOpacity>
</View>
)
};
At first, just console the props in Menu button and check if you get openDrawer or closeDrawer or any other method you are looking for then you can call it.
Everytime i try to set a custom header in stacknavigation the error appears: "cant find variable View". When i replace the component with just a text, it works.
Error appears everytime i use the class "LogoTitle" for the headerTitel
tryied to use a const insted of a class, but dosent worked
const TabNavigation = createMaterialTopTabNavigator({
AllChats: { screen: AllChatsScreen,
navigationOptions: {
tabBarLabel: "Chats",}}});
class LogoTitle extends React.Component {
render() {
return (
<Image
source={require('./spiro.jpg')}
style={{ width: 30, height: 30 }}/>);}}
const SignedInn = createStackNavigator({
TabNavigation: {
screen: TabNavigation,
},},{
navigationOptions: {
headerTitle: <LogoTitle/>}})
export const createRootNavigator = (signedIn = false) => {
return createSwitchNavigator(
{
SignedIn: {
screen: SignedInn}});};
you need to use header instead of headerTitle to be able to use View
const SignedIn = createStackNavigator ({
TabNavigation:{screen: TabNavigation,
navigationOptions: ({navigation}) => ({
header: <LogoTitle navigation= {navigation} />,
})
},
})
I'm trying to add a constant Header bar to my screen (all screens, similar to this example) with basic functionality (menu button, and a back button), but I'm having trouble finding a way to do this when using createBottomTabNavigator. I haven't seen anything saying this isn't possible, so if I'm making a design mistake, do let me know.
Here is my minimal foobar example (it runs):
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { createBottomTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
class ScreenA extends React.Component {
constructor(props) {
super(props);
this.state = {
screenName: 'Screen A'
}
}
render() {
return (
<View
style={styles.container}
>
<Text>{this.state.screenName}</Text>
</View>
);
}
};
class ScreenB extends React.Component {
constructor(props) {
super(props);
this.state = {
screenName: 'Screen B'
}
}
render() {
return (
<View
style={styles.container}
>
<Text>{this.state.screenName}</Text>
</View>
);
}
};
const BottomTabNav = createBottomTabNavigator(
{
ScreenA: {
screen: ScreenA,
navigationOptions: {
title: '',
tabBarIcon: ({ focused, tintColor }) => {
return <Ionicons
name={ focused ? 'ios-card' : 'ios-card-outline' }
size={30}
style={{ marginTop: 6 }}
/>;
},
}
},
ScreenB: {
screen: ScreenB,
navigationOptions: {
title: '',
tabBarIcon: ({ focused, tintColor }) => {
return <Ionicons
name={ focused ? 'ios-chatbubbles' : 'ios-chatbubbles-outline' }
size={30}
style={{ marginTop: 6 }}
/>;
},
},
}
},
{
initialRouteName: 'ScreenA',
}
);
export default class App extends React.Component {
render() {
return (
<BottomTabNav />
);
}
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Apologies for not including a Snack link, but I'm having issues with dependencies when trying to build the demo.
You need your screens to be stack navigators which you then can add in your tab navigator. The "defaultNavigationOptions" are your navigationOptions for the header and the navigationOptions in the code snippet corresponds for the BottomTabNavigator options.
You can read more about this here: https://reactnavigation.org/docs/en/navigation-options-resolution.html
const StackA = createStackNavigator({ ScreenA },
{
defaultNavigationOptions: (navigationOptions),
navigationOptions: {
tabBarLabel: 'Screen A',
},
});
const StackB = createStackNavigator({ScreenA},
{
defaultNavigationOptions: (navigationOptions),
navigationOptions: {
tabBarLabel: 'Screen B',
},
});
const tabNavigator = createBottomTabNavigator({StackA, StackB});
only createStackNavigator has its own header so you have to wrap whatever you want in it, according to your question there is two answers to this,
put the createBottomTabNaviagator in a parent createStackNavigator and access the createBottomTabNavigator children routeName and assign it to the parent createStackNavigator header
const BottomTabNav = createBottomTabNavigator({
FirstScreen: ScreenA,
SecondScreen: ScreenB,
});
BottomTabNav.navigationOptions = ({ navigation }) => {
// By default routeName will come from the BottomTabNav,
// but here we can access the children screens
// and give the parent ParentStack that routeName
const { routeName } = navigation.state.routes[navigation.state.index];
// You can do whatever you like here to pick the title based on the route name
const headerTitle = routeName;
return {
headerTitle,
};
};
const ParentStack = createStackNavigator({
Home: BottomTabNav,
AnotherScreen: AnotherScreen,
});
put the child screens in a createStackNavigator so that every child will have its own header
const ScreenA = createStackNavigator({
FirstScreen: ScreenA,
/* other routes here */
});
const ScreenB = createStackNavigator({
SecondScreen: ScreenB,
/* other routes here */
});
const BottomTabNav = createBottomTabNavigator({
FirstScreen: ScreenA,
SecondScreen: ScreenB,
});
recently I have a problem on a simple DrawerNavigator inside StackNavigator. My goal is to get both the back function from StackNavigator and DrawerNavigator side menu when I click BurgerMenu icon on the StackNavigator's header.
Here's my current code
import React, { Component } from 'react';
import { TouchableHighlight } from 'react-native';
import SvgUri from 'react-native-svg-uri';
import { StackNavigator } from 'react-navigation';
import { DrawerNavigator } from "react-navigation";
import SideBar from './SideBar';
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import Screen3 from './Screen3';
export default class App extends React.Component {
render() {
return <StackNav />;
}
}
class BurgerMenu extends React.Component {
render() {
return (
<TouchableHighlight
onPress={() => this.props.navigation.navigate("DrawerOpen")}>
<SvgUri
width="30"
height="30"
source={require("Project_01/app/images/menu.svg")}
/>
</TouchableHighlight>
);
}
}
const DrawerNav = DrawerNavigator(
{
Screen1: { screen: Screen1 }
},
{
contentComponent: props => <SideBar {...props} />
}
);
const StackNav = StackNavigator(
{
Screen1: {
screen: DrawerNav,
},
Screen2: {
screen: Screen2,
},
Screen3: {
screen: Screen3,
},
},
{
initialRouteName: 'DrawerNav',
navigationOptions: {
headerRight: <BurgerMenu /> ,
title: "Header"
},
},
);
As you can see Im adding headerRight with BurgerMenu component to navigationOptions in StackNavigator.
In BurgerMenu class I added TouchableHighlight with " onPress={() => this.props.navigation.navigate("DrawerOpen")}> " which I mean open up the DrawerNavigator.
Unfortunately when I press that, i got an error message says "undefined is not an object (evaluating 'e.props.navigation.navigate').
Can someone help me please
Try changing this:
navigationOptions: {
headerRight: <BurgerMenu /> ,
title: "Header"
},
by this:
navigationOptions: ({navigation}) => ({
headerRight: <BurgerMenu navigation={navigation} />,
title: "Header"
}),
Also you can refer to this example on my github.