I know this question has been asked before, but only for older versions of react-navigation. Since then a few things have changed. createBottomTabNavigator
makes it much faster to create a bottom navigator and the function jumpToIndex()
doesn't exist anymore.
My Question is how to create an Instagram-like bottom tab, where the first, second, fourth and fifth navigation buttons act like usual tab navigators and the middle button (screen3
) opens the modal screen3Modal
.
I have tried it in react-navigation 3.x.x, using createBottomTabNavigator
and createStackNavigator
.
import React, { Component, } from 'react';
import { createBottomTabNavigator, createStackNavigator, createAppContainer, } from 'react-navigation';
import { Screen1, Screen2, Screen3, Screen4, Screen5 } from './screens';
const TabNavigator = createBottomTabNavigator({
screen1: { screen: Screen1, },
screen2: { screen: Screen2, },
screen3: {
screen: () => null,
navigationOptions: () => ({
tabBarOnPress: () => this.props.navigation.navigate('screen3Modal')
})
},
screen4: { screen: Screen4, },
screen5: { screen: Screen5, },
});
const StackNavigator = createStackNavigator({
Home: { screen: TabNavigator },
screen3Modal: { screen: Screen3, },
},
{
initialRouteName: 'Home',
});
const StackNavigatorContainer = createAppContainer(StackNavigator);
export default class App extends Component {
render() {
return <StackNavigatorContainer />;
}
}
This code creates the tab navigation and modal navigation. The modal can be opened from another screen, but it doesn't work from within the tab navigator. I get the errormessage undefined is not an object (evaluating '_this.props.navigation')
I know this question has been asked before, but only for older versions of react-navigation. Since then a few things have changed. createBottomTabNavigator
makes it much faster to create a bottom navigator and the function jumpToIndex()
doesn't exist anymore.
My Question is how to create an Instagram-like bottom tab, where the first, second, fourth and fifth navigation buttons act like usual tab navigators and the middle button (screen3
) opens the modal screen3Modal
.
I have tried it in react-navigation 3.x.x, using createBottomTabNavigator
and createStackNavigator
.
import React, { Component, } from 'react';
import { createBottomTabNavigator, createStackNavigator, createAppContainer, } from 'react-navigation';
import { Screen1, Screen2, Screen3, Screen4, Screen5 } from './screens';
const TabNavigator = createBottomTabNavigator({
screen1: { screen: Screen1, },
screen2: { screen: Screen2, },
screen3: {
screen: () => null,
navigationOptions: () => ({
tabBarOnPress: () => this.props.navigation.navigate('screen3Modal')
})
},
screen4: { screen: Screen4, },
screen5: { screen: Screen5, },
});
const StackNavigator = createStackNavigator({
Home: { screen: TabNavigator },
screen3Modal: { screen: Screen3, },
},
{
initialRouteName: 'Home',
});
const StackNavigatorContainer = createAppContainer(StackNavigator);
export default class App extends Component {
render() {
return <StackNavigatorContainer />;
}
}
This code creates the tab navigation and modal navigation. The modal can be opened from another screen, but it doesn't work from within the tab navigator. I get the errormessage undefined is not an object (evaluating '_this.props.navigation')
5 Answers
Reset to default 5I have found a relatively easy solution:
Hide the original navigation bar with display:"none"
const TabNavigator = createBottomTabNavigator(
{
screen1: Screen1,
screen2: Screen2,
screen4: Screen4,
screen5: Screen5,
}, {
tabBarOptions: {
style: { display: "none", }
}
},
);
const StackNavigator = createStackNavigator(
{
Home: TabNavigator,
screen3: Screen3
}, {
mode: 'modal',
}
)
export default createAppContainer(StackNavigator);
And create a new navigation bar on each screen
<View style={{ flexDirection: "row", height: 50, justifyContent: "space-evenly", alignItems: "center", width: "100%" }}>
<TouchableOpacity onPress={() => this.props.navigation.navigate("screen1")}><Text>1</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.navigation.navigate("screen2")}><Text>2</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.navigation.navigate("screen3")}><Text>3</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.navigation.navigate("screen4")}><Text>4</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.navigation.navigate("screen5")}><Text>5</Text></TouchableOpacity>
</View>
You can wrap everything inside the same StackNavigator, that way you can navigate to other routes easily. Here i'm passing the screen3 as default route, but you can change that to whatever you'd like.
import React, { Component, } from 'react';
import { createBottomTabNavigator, createStackNavigator, createAppContainer, } from 'react-navigation';
import { Screen1, Screen2, Screen3, Screen4, Screen5 } from './screens';
const TabNavigator = createBottomTabNavigator({
screen1: { screen: Screen1, },
screen2: { screen: Screen2, },
screen3: { screen: () => null, }, //this.props.navigation.navigate('screen3Modal')
screen4: { screen: Screen4, },
screen5: { screen: Screen5, },
});
const StackNavigator = createStackNavigator({
Home: { screen: TabNavigator },
screen3Modal: { screen: Screen3, },
},
{
initialRouteName: 'screen3Modal',
});
const StackNavigatorContainer = createAppContainer(StackNavigator);
export default class App extends Component {
render() {
return <StackNavigatorContainer />;
}
}
Well I have spent hours upon hours to solve this problem.
Here's fully working & tested solution:
- Set up your app container by including tabs stack & to be opened modal navigation stack:
const FinalTabsStack = createStackNavigator(
{
tabs: TabNavigator,
screen1: Screen1Navigator,
}, {
mode: 'modal',
}
)
Create app container with that tabs stack per this guide
Inside the
TabNavigator
in thecreateBottomTabNavigator
return null ponent for specific tab (screen3) (to turn off navigation by react-navigator) and handle the tab manually inside thedefaultNavigationOptions
by creating custom ponent for it.
const TabNavigator = createBottomTabNavigator({
screen1: Screen1Navigator,
screen2: Screen2Navigator,
screen3: () => null,
screen4: Screen4Navigator,
screen5: Screen5Navigator,
}
defaultNavigationOptions: ({ navigation }) => ({
mode: 'modal',
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
if (routeName === 'screen3') {
return <Screen3Tab isFocused={focused} />;
}
},
}),
- Handle click manually inside a custom tab ponent
Screen3Tab
with TouchableWithoutFeedback & onPress. Inside Screen3Tab ponent:
<TouchableWithoutFeedback onPress={this.onPress}>
<Your custom tab ponent here />
</TouchableWithoutFeedback>
- Once you catch onPress dispatch redux event
onPress = () => {
this.props.dispatch({ type: 'NAVIGATION_NAVIGATE', payload: {
key: 'screen3',
routeName: 'screen3',
}})
}
- Handle dispatched event using Navigation Service. I'm using
redux-saga
for it
NavigationService.navigate(action.payload);
A bit plicated but works.
I've found a better solution based on this github issue.
You just have to add in the specific tab configuration, in your case screen3 the event navigationOptions(you already had it), you were pretty close, but, you had to receive the navigation as a parameter, because there isn't any context for this
as you were using it. To correct the first code that you wrote I would change it to this and it would work:
navigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation }) => {
navigation.navigate("screen3Modal");
}
})
what can be done is to prevent default action of tab press event and navigate to your desired screen with desired animation.
In my case, i wanted to open a modal in iOS in presentation style which came from iOS 13. what I did was prevent the default action like below
<Tab.Screen name="screen1"
ponent={ EmptyScreen } // empty screen becuase I want nothing to present here so it was a ponent that returns null which is given bellow
listeners={({ navigation, route }) => ({
tabPress: e => {
e.preventDefault();
navigation.navigate('modalscreen');
},
})} />
const EmptyScreen = () => {
return null
}
'modelscreen' used in above code is from a navigation stack which looks like bellow. Tab navigator for Tab.Screen in the above code is one of the screen in otherstacknavigation which is used in the navigation below.
const MainStack = ({ initialRoute }) => {
return (
<Stack.Navigator
initialRouteName="otherstacknavigation"
screenOptions={{
headerShown: false,
gestureEnabled: true,
cardOverlayEnabled: true,
...TransitionPresets.ModalPresentationIOS
}}>
<Stack.Screen name='modalscreen'
ponent={modalscreen}
options={{
headerShown: false
}} />
<Stack.Screen name='otherstacknavigation'
ponent={otherstacknavigation}
initialParams={{ initialRoute: initialRoute }}/>
</Stack.Navigator>
);
}
export default MainStack;
you can import TransitionPresets like bellow;
import { createStackNavigator, TransitionPresets } from '@react-navigation/stack'
So, in my case, i have first main stack navigator which is MainStack above. It has another stack navigator which is otherstacknavigation in above MainStack. That otherstacknavigation has a screen whose ponent is Tab navigator. and Tab.Screen in above code is from that Tab Navigator. Now, from one of the tab from this tab navigator, i am navigating to 'modelscreen' which is in MainStack. I had to create this MainStack navigator just to achieve this features otherwise my code would start from otherstacknavigation. Because, ModalPresentationIOS can only be applied to stack level not screen level.
I am using react-navigation-5.* and i think it has answered multiple questions that can arise in such situation.