React Native: React Navigation - use the same header component in every screen? - react-native

So, I'm trying to use React Navigation and I want to use the same Header component across all my screens.
I used the expo-cli to create the project.
In my MainTabNavigator.js file, I have a HomeStack and SettingsStack, and they look like this:
const config = Platform.select({
web: { headerMode: "screen" },
default: {}
});
const HomeStack = createStackNavigator(
{
Home: HomeScreen
},
config
);
HomeStack.navigationOptions = {
tabBarIcon: ({ focused }) => (
<NavButton
focused={focused}
name={focused ? "star" : "create"}
label={focused ? "Save" : "New"}
/>
)
};
HomeStack.path = "";
At the bottom of the file, I have my tabNavigator
const tabNavigator = createBottomTabNavigator(
{
HomeStack,
SettingsStack
},
{
tabBarOptions: {
inactiveTintColor: "#fff",
labelStyle: {
fontSize: 14
},
showLabel: false,
style: {
backgroundColor: "#fff",
borderTopColor: "#fff",
height: 70,
paddingBottom: 10,
paddingTop: 10
}
}
}
);
tabNavigator.path = "";
export default tabNavigator;
I tried adding a <Header /> component in the navigatorOptions and defaultNavigationOption above the tabBarOptions in the createBottomTabNavigator function.
Similar to this:
...
{
navigationOptions: {
header: <Header />
},
tabBarOptions: {
inactiveTintColor: "#fff",
labelStyle: {
fontSize: 14
},
...
but this just gives me a blank header... not using my component.
I currently have the functionality I want, but I have to go into each Screen file and add:
HomeScreen.navigationOptions = {
header: <Header />
};
Currently using "react-navigation": "^3.11.0"
Any help is appreciated!

This is how I'm achieving this.
Create a CustomHeader component like this:
const MyHeader = (navigation) => {
return {
header: props => <MyCustomHeader {...props} />,
headerStyle: { backgroundColor: '#fff' },
headerTintColor: '#000',
};
}
Then include it in defaultNavigationOptions in your stack navigator
const AppNavigator = createStackNavigator(
{
Launch: {
screen: LaunchComponent,
}
},
{
initialRouteName: "Launch",
defaultNavigationOptions: ({ navigation }) => {
return MyHeader(navigation)
}
}
)

I wanted to achieve similar solution for DrawerNavigator. The only way to do this was to create HOC component.
import React, {Fragment} from 'react';
const NavHeader = props => {
// ... NavHeader code goes here
};
export const withHeader = Component => {
return props => {
return (
<Fragment>
<NavHeader {...props} />
<Component {...props} />
</Fragment>
);
};
};
Then in your Drawer you do:
<Drawer.Navigator>
<Drawer.Screen
name={ROUTES.DASHBOARD}
component={withHeader(DashboardContainer)} // <--- Wrap it around component here.
/>
</Drawer.Navigator>

Set your StackNavigator as parent of your BottomTabNavigator, doing it will let you have a single header for all the bottomTabs.
If you use this approach, and need for some reason to have a backButton on your header you have to change it manually yourself.
const bottomTabNavigator = createBottomTabNavigator(bottomRoutesConfig, bottomNavigatorConfigs)
createStackNavigator:({ tabNavigator : bottomTabNavigator },{ defaultNavigationOptions :{ header : <Header/> }})
Doing this you can use one global header for all the screens, but as said, to change something on the header based off the screen you have to change it yourself, making the header know in what location the user currently is.

set header to null in every screen and load your component on each screens
class HomeScreen extends Component {
static navigationOptions = {
header: null,
};
render(){
return(
<View style={{flex:1}}>
<Header />
</View>
)
}
}
so, the default header of react-navigation will be null and you can load your custom component as header
OR
class HomeScreen extends React.Component {
static navigationOptions = {
// headerTitle instead of title
headerTitle: <Header />,
};
/* render function, etc */
}
use headerTitle instead of title to load your custom component

Related

React Navigation - undefined is not an object (evaluating 'this.navigation.navigate')

I am following this tutorial to implement a switch navigator for user authentication: https://snack.expo.io/#react-navigation/auth-flow-v3.
However, this.navigation.navigate appears to undefined when I try to navigate to the next screen.
undefined is not an object (evaluating 'this.props.navigation.navigate')
I am using expo for my app, and I've already looked at the solutions posted to a similar question at React Native - navigation issue "undefined is not an object (this.props.navigation.navigate)" to no avail.
import * as React from 'react';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import profile from './app/screens/profile.js'
import home from './app/screens/home.js'
import createCompetition from './app/screens/create.js'
import explore from './app/screens/explore.js'
import Icon from 'react-native-vector-icons/MaterialIcons'
import login from './app/screens/login.js';
import { f } from './config/config.js';
import { ActivityIndicator, AsyncStorage, Button, StatusBar, StyleSheet, View } from 'react-native';
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
/**
* Tab Stack is the Bottom Navigator for the different pages
*/
const TabStack = createBottomTabNavigator(
{
Home: {
screen: home,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="home" size={25} style={{ color: tintColor }} />
),
},
},
Explore: {
screen: explore,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="search" size={25} style={{ color: tintColor }} />
),
}
},
Profile: {
screen: profile,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="person" size={25} style={{ color: tintColor }} />
),
}
},
Create: {
screen: createCompetition,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="add" size={25} style={{ color: tintColor }} />
),
}
},
},
{
tabBarOptions: {
showIcon: true,
showLabel: false,
activeTintColor: 'black',
style: { backgroundColor: 'white', }
},
},
)
/**
* Loading Screen during authorization process
*/
class AuthLoadingScreen extends React.Component {
constructor() {
super();
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
f.auth().onAuthStateChanged(function (user) { //checks if user is signed in or out
this.props.navigation.navigate(user ? 'App' : 'Auth');
})
};
// Render any loading content that you like here
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStack = createStackNavigator({ Home: TabStack });
const AuthStack = createStackNavigator({ Login: login });
const RootStack = createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
const App = createAppContainer(RootStack);
export default App;
You are not giving access to this to your _bootstrapAsync function and your onAuthStateChanged callback. Just pass the callback inside of it using arrow function, as it autobinds the current function to the current app this
_bootstrapAsync = async () => {
f.auth().onAuthStateChanged((user) => { //checks if user is signed in or out
this.props.navigation.navigate(user ? 'App' : 'Auth');
})
};
The problem is with function keyword, which doesnt bind this keyword. Better replace it with ES6 arrow functions which implictly binds this to the inner scope :
f.auth().onAuthStateChanged((user) => { //checks if user is signed in or out
this.props.navigation.navigate(user ? 'App' : 'Auth');
})
Hope it helps .feel free for doubts

How to pass props to screens in TabNavigator?

How do I pass props to my screen components in TabNavigator? And with that I mean FindDestinationScreen.
I would like to somehow have a HOC that wraps <AppContainer> and passes props to my screens.
Ive tried different solutions that people has answered to similar questions here but can't get it to work..
Here is my code and this is my app.js:
import { SafeAreaView, View } from "react-native";
import { createAppContainer } from "react-navigation";
import { createMaterialBottomTabNavigator } from "react-navigation-material-bottom-tabs";
import Icon from "react-native-vector-icons/Ionicons";
import FindDestinationScreen from "./src/screens/FindDestinationScreen";
import CurrentTripScreen from "./src/screens/CurrentTripScreen";
const TabNavigator = createMaterialBottomTabNavigator(
{
FindDestinationScreen: {
screen: FindDestinationScreen,
navigationOptions: {
title: "Search",
tabBarIcon: ({ tintColor }) => (
<SafeAreaView>
<Icon
style={[{ color: tintColor }]}
size={25}
name={"ios-search"}
/>
</SafeAreaView>
)
}
},
CurrentTripScreen: {
screen: CurrentTripScreen,
navigationOptions: {
title: "Trip",
tabBarIcon: ({ tintColor }) => (
<SafeAreaView>
<Icon style={[{ color: tintColor }]} size={25} name={"ios-train"} />
</SafeAreaView>
)
}
},
{
shifting: false,
labeled: true,
initialRouteName: "FindDestinationScreen",
activeColor: "#ffffff",
inactiveColor: "#000",
barStyle: { backgroundColor: "#456990", height: 80, paddingTop: 10 }
}
);
const AppContainer = createAppContainer(TabNavigator);
export default App = () => {
return <AppContainer />;
};
What I want to happen: Let's say that if I write <AppContainer testProp="testString" />. Is it possible to retrieve this prop in the screens written in TabNavigator? I mean is it possible to access the prop if I write it in some other way?
What I have tried: I have tried the code above and also just the prop in TabNavigator like so <FindDestionationScreen testProp="testString">
This works but I dont want to write this on every screen.
If you want to use Context API, create your context, and wrap your root app component/AppStack with the provider then you can have access to all states in any of your screens/pages by using useContext hook. For example;
<ImageProvider>
<FoodProvider>
<EventProvider>
<AppProvider>
<AppStack/>
</AppProvider>
</EventProvider>
</FoodProvider>
</ImageProvider>
Hope that helps.

React Navigation drawer doesn't display the title of the current page

I created 2 pages namely Home and Profile and added these two pages to createDrawerNavigation. And then added this drawer navigation to a stack navigator. Now when I try to display the title on the header of the current page. I'm unable to do it.
Here is my code.
Tried DefaultNavigationOptions and got the props value of navigator but doesn't work that way.
const MenuStack = createDrawerNavigator(
{
HomePage: {
screen: Home
},
ProfilePage: {
screen: Profile
}
},
{
drawerWidth: Math.round(Dimensions.get("window").width) * 0.75,
contentOptions: {
activeTintColor: theme.colors.accent
}
}
);
const AppStack = createStackNavigator(
{
MenuStack: {
screen: MenuStack
}
},
{
defaultNavigationOptions: ({ navigation }) => {
return {
headerLeft: (
<Icon
style={{ paddingLeft: 10 }}
onPress={() => navigation.toggleDrawer()}
name="bars"
size={30}
/>
),
headerRight: (
<Icon
style={{ paddingRight: 10 }}
onPress={() => navigation.toggleDrawer()}
name="search"
size={30}
/>
),
headerTitle: navigation.state.routeName,
headerStyle: {
backgroundColor: theme.colors.accent
}
};
}
}
);
export const createRootNavigator = (signedIn = false) => {
return createAppContainer(
createSwitchNavigator(
{
SignedIn: {
screen: AppStack
},
SignedOut: {
screen: WelcomeStack
},
SignInAndOut: {
screen: AuthStack
}
},
{
initialRouteName: signedIn ? "SignedIn" : "SignedOut"
}
)
);
};
I added a headerTitle property in the createStackNavigator but I tell the "menustack" as the title for every page.
Expected the title of the page on the header.
You have 2 options:
1. Render your own header component in each screen
You can render a header inside your screens, either a custom one or one from a component library such as react native paper.
function Home() {
return (
<React.Fragment>
<MyCustomHeader title="Home" />
{{ /* screen content */ }}
</React.Fragment>
);
}
2. Create a stack navigator for each screen that needs to have header
const MenuStack = createDrawerNavigator({
HomePage: {
screen: createStackNavigator({ Home })
},
ProfilePage: {
screen: createStackNavigator({ Profile })
}
});
Keep in mind that while this requires less code, it's also less performant.

createBottomTabNavigator can't change tab from route 3 to route 2

I use createBottomTabNavigator in react navigation v3 and I have 3 route like that:
const Route = createBottomTabNavigator(
{
Home: {
screen: HomeRoute
}
Post: {
screen: PostRoute
},
Mark: {
screen: MarkRoute
},
}
)
but the problem or better say bug comes when I want to navigate from tab Mark to Post that doesn't navigate and change tab :(
any body can solve this problem? thanks!
For navigation, you use the navigate() function of the prop of the button you are using. For example,
If we define our createBottomTabNavigator to be,
export default createBottomTabNavigator(
{
Home: HomeScreen,
Settings: SettingsScreen,
}
);
We would move to the Settings tab using the navigate function of the button as seen below,
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home!</Text>
<Button
title="Go to Settings"
onPress={() => this.props.navigation.navigate('Settings')}
/>
</View>
);
}
}
Here are more detailed examples, TAB-BASED-NAVIGATION
Define your route like this
const Route = createBottomTabNavigator(
{
Home: HomeRoute,
Post: PostRoute,
Mark: MarkRoute,
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
return <View/>
},
}),
tabBarOptions: {
activeTintColor: 'red',
inactiveTintColor: 'gray'
style: {
backgroundColor: 'black'
},
labelStyle: {
fontSize: 12
},
},
}
);

withNavigation can only be used on a view hierarchy of a navigator

I'm getting the error:
Invariant Violation: withNavigation can only be used on a view
hierarchy of a navigator. The wrapped component is unable to get
access to navigation from props or context
I don't know why, because I'm using withNavigation in other components in my app and it works. I don't see a difference in the components that it works on to the one that causes the error.
Code:
the component:
const mapStateToProps = (state: State): Object => ({
alertModal: state.formControls.alertModal
})
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => {
return bindActionCreators(
{
updateAlertModalHeight: updateAlertModalHeight,
updateAlertModalIsOpen: updateAlertModalIsOpen,
updateHasYesNo: updateAlertModalHasYesNo
},
dispatch
)
}
class AlertModalView extends Component<AlertModalProps, State> {
render(): Node {
return (
<View style={alertModalStyle.container}>
<PresentationalModal
style={presentationalModalStyle}
isOpen={this.props.alertModal.isOpen}
title={this.props.alertModal.title}
navigation={this.props.navigation}
updateHasYesNo={this.props.updateHasYesNo}
message={this.props.alertModal.message}
updateAlertModalHeight={this.props.updateAlertModalHeight}
viewHeight={this.props.alertModal.viewHeight}
hasYesNo={this.props.alertModal.hasYesNo}
yesClicked={this.props.alertModal.yesClicked}
updateAlertModalIsOpen={this.props.updateAlertModalIsOpen}
/>
</View>
)
}
}
// $FlowFixMe
const AlertModalViewComponent = connect(
mapStateToProps,
mapDispatchToProps
)(AlertModalView)
export default withNavigation(AlertModalViewComponent)
the stackNavigator:
import React from 'react'
import { View, SafeAreaView } from 'react-native'
import Icon from 'react-native-vector-icons/EvilIcons'
import Add from '../product/add/view'
import Login from '../user/login/view'
import Search from '../product/search/query/view'
import { Image } from 'react-native'
import { StackNavigator, DrawerNavigator, DrawerItems } from 'react-navigation'
const AddMenuIcon = ({ navigate }) => (
<View>
<Icon
name="plus"
size={30}
color="#FFF"
onPress={() => navigate('DrawerOpen')}
/>
</View>
)
const SearchMenuIcon = ({ navigate }) => (
<Icon
name="search"
size={30}
color="#FFF"
onPress={() => navigate('DrawerOpen')}
/>
)
const Stack = {
Login: {
screen: Login
},
Search: {
screen: Search
},
Add: {
screen: Add
}
}
const DrawerRoutes = {
Login: {
name: 'Login',
screen: Login
},
'Search Vegan': {
name: 'Search',
screen: StackNavigator(Stack.Search, {
headerMode: 'none'
}),
navigationOptions: ({ navigation }) => ({
drawerIcon: SearchMenuIcon(navigation)
})
},
'Add vegan': {
name: 'Add',
screen: StackNavigator(Stack.Add, {
headerMode: 'none'
}),
navigationOptions: ({ navigation }) => ({
drawerIcon: AddMenuIcon(navigation)
})
}
}
const CustomDrawerContentComponent = props => (
<SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}>
<View>
<Image
style={{
marginLeft: 20,
marginBottom: 0,
marginTop: 0,
width: 100,
height: 100,
resizeMode: 'contain'
}}
square
source={require('../../images/logo_v_white.png')}
/>
</View>
<DrawerItems {...props} />
</SafeAreaView>
)
const Menu = StackNavigator(
{
Drawer: {
name: 'Drawer',
screen: DrawerNavigator(DrawerRoutes, {
initialRouteName: 'Login',
drawerPosition: 'left',
contentComponent: CustomDrawerContentComponent,
contentOptions: {
activeTintColor: '#27a562',
inactiveTintColor: 'white',
activeBackgroundColor: '#3a3a3a'
}
})
}
},
{
headerMode: 'none',
initialRouteName: 'Drawer'
}
)
export default Menu
Here I render the StackNavigator which is Menu in my app component:
import React, { Component } from 'react'
import Menu from './menu/view'
import Props from 'prop-types'
import { Container } from 'native-base'
import { updateAlertModalIsOpen } from './formControls/alertModal/action'
import AlertModalComponent from './formControls/alertModal/view'
import UserLoginModal from './user/login/loginModal/view'
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => {})
store.dispatch(this.props.fetchUserGeoCoords())
store.dispatch(this.props.fetchSearchQueryPageCategories())
store.dispatch(this.props.fetchCategories())
}
componentWillUnmount() {
this.unsubscribe()
}
render(): Object {
return (
<Container>
<Menu store={this.context} />
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
<UserLoginModal />
</Container>
)
}
}
Vepo.contextTypes = {
store: Props.object
}
export default Vepo
and my root component:
export const store = createStore(
rootReducer,
vepo,
composeWithDevTools(applyMiddleware(createEpicMiddleware(rootEpic)))
)
import NavigationService from './navigationService'
export const App = () => (
<Provider store={store}>
<Vepo
fetchUserGeoCoords={fetchUserGeoCoords}
fetchSearchQueryPageCategories={fetchSearchQueryPageCategories}
fetchCategories={fetchCategories}
/>
</Provider>
)
AppRegistry.registerComponent('vepo', () => App)
I have changed my Vepo component to this to implement the answer by vahissan:
import React, { Component } from 'react'
import Menu from './menu/view'
import Props from 'prop-types'
import { Container } from 'native-base'
import { updateAlertModalIsOpen } from './formControls/alertModal/action'
import AlertModalComponent from './formControls/alertModal/view'
import UserLoginModal from './user/login/loginModal/view'
import NavigationService from './navigationService'
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => {})
store.dispatch(this.props.fetchUserGeoCoords())
store.dispatch(this.props.fetchSearchQueryPageCategories())
store.dispatch(this.props.fetchCategories())
}
componentWillUnmount() {
this.unsubscribe()
}
render(): Object {
return (
<Container>
<Menu
store={this.context}
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef)
}}>
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
</Menu>
<UserLoginModal />
</Container>
)
}
}
Vepo.contextTypes = {
store: Props.object
}
export default Vepo
No errors, but the alertModal no longer displays
In react-navigation, the main StackNavigator creates a context provider and the navigation prop will be made available to any component below its level in the component tree if they use the context consumer.
Two ways to access the navigation prop using context consumer is to either add the component to the StackNavigator, or use the withNavigation function. However because of how React's context API works, any component that uses withNavigation function must be below the StackNavigator in the component tree.
If you still want to access the navigation prop regardless of the position in component tree, you will have to store the ref to the StackNavigator in a module. Following guide from react-navigation will help you do that https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
If you are using react-navigation version 5, use useNavigation hook with a functional component. This hook injects the navigation object into the functional component. Here is the link to the docs:
https://reactnavigation.org/docs/use-navigation/
Vahissan's answer is correct but I could not get it working because of various differences in my code from https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html like my stackNavigator not being a component, it is just an object.
What I managed to do was get the AlertModal component to be a child of the stackNavigator and thus receive the navigation prop by adding it to my StackNavigator's ContentComponent. The code is as above but int the CustomDrawerContentComponent just making it like this:
const CustomDrawerContentComponent = props => (
<SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}>
<View>
<Image
style={{
marginLeft: 20,
marginBottom: 0,
marginTop: 0,
width: 100,
height: 100,
resizeMode: 'contain'
}}
square
source={require('../../images/logo_v_white.png')}
/>
</View>
<DrawerItems {...props} />
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
</SafeAreaView>
)
For anyone who might be interested, you need to npm install #react-navigation/native #react-navigation/compat #react-navigation/stack for withNavigation to work in the recent version