React Native Open Tab Bar in Modal (using expo) - react-native

I'm trying to have one of the tab bar items open as modal when clicked, I'm currently using expo. I've read this: How do i make a TabNavigator button push a modal screen with React Navigation. However, I'm still learning React Native and I'm honestly not sure how to use this using expo navigation. Currently, I have created a stack navigator using "createStackNavigator" for each of the screens. And lastly, I have exported a bottom tab navigator including all of the stacks:
export default createBottomTabNavigator({
Tab1Stack,
Tab2Stack,
Tab3Stack,
Tab4Stack
});
I need Tab4 to open as a modal. Can someone people help me with this? Thank you in advance!!

Note this was built for "react-navigation": "3.3.0" so your mileage my vary for more recent versions of Expo and react-navigation.
To make a modal appear when you tap on the last tab in a TabNavigator requires you to nest your TabNavigator inside a StackNavigator.
So we could set up something like this:
#App.js
A simple App.js.
import React from 'react';
import AppContainer from './MainNavigation';
export default class App extends React.Component {
constructor (props) {
super(props);
this.state = {
};
}
render () {
return (
<AppContainer />
);
}
}
#MainNavigation.js
This file contains two navigators. A TabNavigator and a StackNavigator. The TabNavigator is nested inside the StackNavigator.
To be able to show the ModalScreen we have to override the tabBarOnPress function inside the defaultNavigationOptions which is inside the config for the TabNavigator.
We need to check the navigation.state.key to see where we are navigating too. If we are going to Tab3 we can intercept the call and navigate to the ModalScreen instead. Otherwise we use the defaultHandler and go to the tab that was tapped.
import Screen1 from './Screen1';
import Screen2 from './Screen2';
import Screen3 from './Screen3';
import ModalScreen from './ModalScreen';
import { createBottomTabNavigator, createAppContainer, createStackNavigator } from 'react-navigation';
const screens = {
Tab1: {
screen: Screen1
},
Tab2: {
screen: Screen2
},
Tab3: {
screen: Screen3
}
};
const config = {
headerMode: 'none',
initialRouteName: 'Tab1',
defaultNavigationOptions: {
tabBarOnPress: (data) => {
// this is where the magic happens
const { navigation, defaultHandler } = data;
// we check to see if the navigation key is going to be on Tab3
if (navigation.state.key === 'Tab3') {
// if it is we show the ModalScreen by navigating to it
navigation.navigate('ModalScreen');
} else {
// otherwise we call the defaultHandler and navigate to the tab we pressed
defaultHandler(navigation.state.key);
}
}
}
};
const TabNavigator = createBottomTabNavigator(screens, config);
const stackScreens = {
Tabs: {
screen: TabNavigator
},
ModalScreen: {
screen: ModalScreen
}
};
//we need to set the mode to be modal
const stackConfig = {
headerMode: 'none',
initialRouteName: 'Tabs',
mode: 'modal'
};
const MainNavigator = createStackNavigator(stackScreens, stackConfig);
export default createAppContainer(MainNavigator);
#Screen.js
A simple screen for each tab
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
export default class Screen extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>Tab Screen</Text>
</View>
);
}
}
#ModalScreen
This screen is the modal that will appear when the tab for the third screen is tapped.
As it is part of the StackNavigator, defined above, it has access to the navigation prop. We set up a simple button that when pressed calls this.props.navigation.goBack() This will dismiss the modal.
import React from 'react';
import { View, StyleSheet, Text, Button } from 'react-native';
export default class Screen extends React.Component {
render () {
return (
<View style={styles.container}>
<Text>Modal Screen</Text>
<Button
title={'close modal'}
onPress={() => this.props.navigation.goBack()}
/>
</View>
);
}
}
Here is a snack with it working, https://snack.expo.io/#andypandy/show-modal-on-tab-press, hopefully it will show you how to set it up.

Related

React-native with react-navigation: header not showing in screen

I do have a simple React-native app, using react-navigation for navigation. My issue is that for one of the screens the header and title is not showing, even though it is set in navigationOptions:
main.js:
import React from "react";
import { View, Text } from "react-native";
import {
createDrawerNavigator,
createAppContainer,
createStackNavigator
} from "react-navigation";
import ArticleList from "./screens/ArticleList";
import ArticleList2 from "./screens/ArticeList2";
import ArticleWebView from "./screens/ArticleWebView";
// create the Navigator to be used
const Stack = createStackNavigator({
ArticleList: { screen: ArticleList },
ArticleWebView: { screen: ArticleWebView }
});
const Drawer = createDrawerNavigator(
{
Stack: { screen: Stack },
ArticleList2: { screen: ArticleList2 }
},
{
initialRouteName: "Stack"
}
);
// The container for the navigator
const AppContainer = createAppContainer(Drawer);
class App extends React.Component {
render() {
return <AppContainer />;
}
}
export default App;
The ArticleList2 is a very simple component:
import React from "react";
import { View, Text } from "react-native";
import ArticleListComponent from "../components/ArticleListComponent";
class ArticleList2 extends React.Component {
static navigationOptions = {
title: 'Link2'
};
render() {
return (
<View>
<Text>LIst2</Text>
</View>
);
}
}
export default ArticleList2;
However, the title and header are not rendered in the app. Could anybody help please?
DrawerNavigator does not include a header. In order to create a header inside of ArticleList2 you will have to create a new StackNavigator for the component.
You have to think about and plan out the navigation flow of your app.
The Drawer section of the React Navigation documentation is a little lacking, but you can use the same principle as described in the Tab section
A stack navigator for each tab
const ArticleList2Stack = createStackNavigator({
ArticleList2: { screen: ArticleList2 }
});
const Drawer = createDrawerNavigator(
{
Stack: { screen: Stack },
ArticleList2: { screen: ArticleList2Stack }
},
{
initialRouteName: "Stack"
}
);

React navigation. Render extra views on top of navigator

I'm trying to create a React Native app that renders an area with interchangeable screens and fixed view with some additional data/menu. I tried this solution form official React Navigation, but I can't make it work.
import React, { Component } from "react";
import {createStackNavigator} from 'react-navigation';
import { Text, View,} from 'react-native';
import Details from './DetailsScreen';
import MainScreen from './MainScreen';
const RootStack = createStackNavigator({
Main: {screen: MainScreen,
navigationOptions: {
header: null
}
},
Details: {
screen: Details,
},);
class App extends Component {
static router = {
...RootStack.router,
getStateForAction: (action, lastState) => {
return MyStack.router.getStateForAction(action, lastState);
}
};
render() {
const { navigation } = this.props;
return(
<View>
<Text>Foo</Text> //this is rendering
<RootStack navigation={navigation}/> //this is not
</View>);
}
}
export default App;
Article form link suggests that I can wrap object created with createStackNavigator() in a parent View, but it renders only when it is the only thing returned from render(), otherwise it seems to be ignored. Example from link claims that it is possible to "render additional things", but I can't make it work. Is it possible to do this way?
You can try this:
//wrapper.js
function wrapNavigator(Navigator, Wrapper, wrapperProps = {}) {
const WrappedComponent = props => (
<Wrapper { ...props, ...wrapperProps}>
<Navigator {...props} />
</Wrapper>
);
WrappedComponent.router = Navigator.router;
WrappedComponent.navigationOptions = Navigator.navigationOptions;
return WrappedComponent;
};
//app.js
const App = (props) => <View>
<Text>Some text...</Text>
{children}
</View>
export default wrapNavigator(Stack, App);
But this will not work if there are no parent navigator for App component.

How to lock orientation for specific screen on TabNavigator

I used TabNavigation for my app. In some screen, I want it only display on portrait orientation, and others are landscape. I've found react-native-orientation and try in my screens:
Screen 1
import React from "react";
import Orientation from 'react-native-orientation';
import { Text } from 'react-native'
export default Screen1 extends React.PureComponent{
componentDidMount(){
Orientation.lockToLandscape();
}
componentWillUnmount(){
Orientation.unlockAllOrientations();
}
render() {
return(<Text>Screen 1</Text>);
}
}
Screen 2
import React from "react";
import Orientation from 'react-native-orientation';
import { Text } from 'react-native'
export default Screen2 extends React.PureComponent{
componentDidMount(){
Orientation.lockToPortrait();
}
componentWillUnmount(){
Orientation.unlockAllOrientations();
}
render() {
return(<Text>Screen 2</Text>);
}
}
Tab Navigator
const AppNavigator = TabNavigator( {
Screen1: { screen: Screen1 },
Screen2: { screen: Screen2 },
});
But it always portrait, which mean that its orientation always set base on orientation of last screen I add in TabNavigator. Any help is appreciate, thank you in advance!
Edit 1
I've try StackNavigation instead, and it work. Still don't know why it's not run with TabNavigation.
It has been long time since I asked this questions, and react-navigation is upgrade to version 3. My solution for this problem is set orientation for tab each time it's pressed, cause tab screen isn't unmount when you click on other tab. To fix that, you can add tabBarOnPress to your navigationOptions of your screen like this
import Orientation from 'react-native-orientation';
...
class TabbarScreen1 extends Component {
static navigationOptions = {
...,
tabBarOnPress: ({ defaultHandler }) => {
// Orientation.lockToLandscape();
Orientation.lockToPortrait();
defaultHandler();
},
}
...
}

goBack always taking to home/first screen in react-navigation

I am working on an app but goback button is not taking me to the back screen.
App.js
import React, { Component } from 'react';
import one from './components/test/one';
import two from './components/test/two';
import three from './components/test/three';
import { DrawerNavigator } from 'react-navigation';
const AppNavigator = DrawerNavigator({
one: {
screen: one,
},
two: {
screen: two,
},
three: {
screen: three,
},
}
);
export default () =>
<AppNavigator />;
components/test/one.js
import React, { Component } from 'react';
import { Button } from 'react-native';
export default class one extends React.Component {
static navigationOptions = {
drawerLabel: 'one',
};
render() {
return (
<Button
onPress={() => this.props.navigation.navigate('two')}
title="Go to Two"
/>
);
}
}
components/test/two.js
import React, { Component } from 'react';
import { Button } from 'react-native';
export default class two extends React.Component {
static navigationOptions = {
drawerLabel: 'two',
};
render() {
return (
<Button
onPress={() => this.props.navigation.navigate('three')}
title="Go to 3"
/>
);
}
}
components/test/three.js
import React, { Component } from 'react';
import { Button } from 'react-native';
export default class three extends React.Component {
static navigationOptions = {
drawerLabel: 'three',
};
render() {
return (
<Button
onPress={() => this.props.navigation.goBack()}
title="Go back to 2"
/>
);
}
}
click on "Go to two" => "Go to 3" => "Go back to 2".
on screen 3, clicking on the "Go back to 2" is always taking me to one.js.
It is not even working if I combine them in one single file.
You are either using the wrong Navigator comp or your requirements are not clear. Basically, You would like to use the StackNavigator for your desired behavior.
The catch is, DrawerNavigator is used to build up a drawer menu. If you swipe from the left you'll see your navigator drawer containing all of your screens, as you can see in the image below.
If you add a button on your screen like below, you'll see your menu open.
<Button title="MENU" onPress={() => this.props.navigation.navigate('DrawerOpen')} />
The conclusion is, whenever we use DrawerNavigator we always go back to initial route, which is whatever we defined as the first item or using the initialRouteName key of the second param of the DrawerNavigator.
It's only the StackNavigator that supports this stacking order you would like to achieve, as the name suggests itself.
What you can do is to wrap a new StackNavigator inside one of the screens of the DrawerNavigator. For example:
const AppNavigator = DrawerNavigator({
drawer1: {
screen: drawer1,
}
});
const drawer1 = StackNavigator({
one: { screen: one },
two: { screen: two },
three: { screen: three },
});

How to use the Drawer and Tab navigation together in react-native with ex-navigation

I'm building an app in react-native and implement navigation with ex-navigation. I want to use the TabNav to offer quick access to the most used features and to use the DrawerNav for features that should be available but are not used that often.
I searched and tried myself but I cannot have a drawer and tab navigation together. The ex-navigation example app has both but not together. It starts with the drawer nav and when I click on the Tab nav example the drawer icon is gone.
Does anyone have some tips/hints how to get this done?
Have you tried putting the Navigation Tab Screen inside the Navigation?
Router.js
import React, { Component } from 'react';
import { createRouter } from '#expo/ex-navigation';
import NavigationDrawer from './NavigationDrawer';
import NavigationTab from './NavigationTab';
export const Router = createRouter(() => ({
navigationDrawer : () => NavigationDrawer,
navigationTab : () => NavigationTab
}));
NavigationDrawer.js
import React, { Component } from 'react';
import {
StackNavigation,
DrawerNavigation,
DrawerNavigationItem,
} from '#expo/ex-navigation';
import { Router } from './Router';
export default class NavigationDrawer extends Component{
render(){
return(
<DrawerNavigation
drawerPosition="left"
drawerWidth={300}
initialItem="navigationTab" >
<DrawerNavigationItem
id="home"
selectedStyle={{backgroundColor: '#E8E8E8',}}
renderTitle={isSelected => renderTitle('Home', isSelected)}
renderIcon={isSelected => renderIcon('home', isSelected)}
>
<StackNavigation
id="navigationTab"
initialRoute={Router.getRoute('navigationTab')}
defaultRouteConfig={{
navigationBar: {
backgroundColor: '#0084FF',
tintColor: '#fff',
title: 'NavigationTab',
},
}}
/>
</DrawerNavigationItem>
</DrawerNavigation >
);
}
}
NavigationTab.js
import React, { Component } from 'react';
import {
StackNavigation,
TabNavigation,
TabNavigationItem,
} from '#expo/ex-navigation';
import { Router } from './Router';
export default class NavigationTab extends Component {
render() {
return (
<TabNavigation
initialTab="first"
initialItem="first"
<TabNavigationItem
id="first"
title="First"
>
{/*content*/}
</TabNavigationItem>
<TabNavigationItem
id="second"
title="Second"
>
{/*content*/}
</TabNavigationItem>
<TabNavigationItem
id="third"
title="Third"
>
{/*content*/}
</TabNavigationItem>
</TabNavigation>
)
}
}
With this approach, Drawer and Tab Navigation can be used together, but when you select an item from the drawer menu (with stack navigation), the screen with tab will be replaced.
If you want it the tab to exists in each screen from the drawer menu, you'll need to add the tab in each of those screen (which I think should be unnecessary).