Nested tab navigators don't work inside drawer navigator - reactjs

I'm trying to embed tab navigators within a drawer navigator, but the drawer navigator sometimes stops working. Code: https://github.com/myplaceonline/testreactexpo/tree/drawernestedtabs
To reproduce the problem:
Click the Screen2 tab at the bottom
Open the drawer
Click Home
Open the drawer
Click Screen2
Nothing happens
Is there a better way to do this and keep the drawer and bottom tabs synchronized and avoid the issue where the drawer stops working?
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
createAppContainer,
createBottomTabNavigator,
createDrawerNavigator,
createStackNavigator,
NavigationActions,
DrawerActions,
} from 'react-navigation';
import { Ionicons } from '#expo/vector-icons';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#ffffff",
alignItems: "center",
justifyContent: "center",
},
});
class HomeScreen extends React.Component {
static navigationOptions = {
title: "Home",
};
render() {
return (
<View style={styles.container}>
<Text>Home</Text>
</View>
);
}
}
class Screen2Screen extends React.Component {
static navigationOptions = {
title: "Screen2",
};
render() {
return (
<View style={styles.container}>
<Text>Screen2</Text>
</View>
);
}
}
const AppScreensTabs = {
Home: HomeScreen,
Screen2: Screen2Screen,
};
const AppScreensTabOptions = {
tabBarOptions: {
showLabel: true,
},
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
let iconName;
// https://expo.github.io/vector-icons/
if (routeName === "Home") {
iconName = "md-home";
} else if (routeName === "Screen2") {
iconName = "md-beer";
}
return <Ionicons name={iconName} size={25} color={tintColor} />;
},
}),
};
const AppScreens = {
TabHome: createBottomTabNavigator(
AppScreensTabs,
Object.assign(
{ initialRouteName: "Home" },
AppScreensTabOptions,
)
),
TabScreen2: createBottomTabNavigator(
AppScreensTabs,
Object.assign(
{ initialRouteName: "Screen2" },
AppScreensTabOptions,
)
),
};
const AppScreensStackNavigationOptions = {
defaultNavigationOptions: ({ navigation }) => ({
headerLeft: <Ionicons name="md-menu" size={25} onPress={ () => navigation.openDrawer() } style={{ marginLeft: 15 }} />
})
};
const AppDrawer = createAppContainer(createDrawerNavigator({
DrawerHome: {
screen: createStackNavigator(
AppScreens,
Object.assign(
{ initialRouteName: "TabHome" },
AppScreensStackNavigationOptions,
)
),
navigationOptions: {
drawerLabel: "Home",
}
},
DrawerScreen2: {
screen: createStackNavigator(
AppScreens,
Object.assign(
{ initialRouteName: "TabScreen2" },
AppScreensStackNavigationOptions,
)
),
navigationOptions: {
drawerLabel: "Screen2",
}
},
}));
export default class App extends React.Component {
render() {
return (
<AppDrawer />
);
}
}
<div data-snack-id="#git/github.com/myplaceonline/testreactexpo#drawernestedtabs" data-snack-platform="ios" data-snack-preview="true" data-snack-theme="light" style="overflow:hidden;background:#fafafa;border:1px solid rgba(0,0,0,.08);border-radius:4px;height:505px;width:100%"></div>
<script async src="https://snack.expo.io/embed.js"></script>

It seems you're trying to keep drawer state and tab state in sync, but I guess that from a UX perspective, it might make more sense to treat them as separate navigation containers, each with their own navigation hierarchy. Keeping them in sync is not straight-forward using react-navigation and is not a pattern I think people will be familiar with when navigation through your app.

Related

Cannot render images in Tab Navigator under react navigation in React Native

/**
* Author: Rahul Shetty
* Date: 10/10/2019
*
* Configure app routes
* #flow
*/
import React from 'react';
import { Image, StyleSheet } from 'react-native';
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { Recipes, Restaurants } from 'containers/root';
import FeedIcon from 'assets/icons/feed/icons-feed.png';
import RestaurantIcon from 'assets/icons/restaurant/icons-restaurant.png';
import routes from './routes';
type Screens = {
[string]: React$Node,
};
const styles = StyleSheet.create({
iconStyle: { width: 28, height: 28 },
});
const { RECIPES, RESTAURANTS } = routes;
const IMAGE_PATH = {
[RECIPES]: FeedIcon,
[RESTAURANTS]: RestaurantIcon,
};
// Common navigation options for stack navigator
const commonNavOptions = {
defaultNavigationOptions: {
gesturesEnabled: false,
drawerLockMode: 'locked-closed',
},
headerMode: 'none',
};
const TabBarComponent = ({ navigation, tintColor }) => {
const { routeName } = navigation.state;
// You can return any component that you like here!
return <Image style={styles.iconStyle} source={IMAGE_PATH[routeName]} />;
};
// A function that returns a stack of navigation screens on providing the Object of screen and it's path name
const StackNav = (screens: Screens) =>
createStackNavigator(screens, commonNavOptions);
// Screens for Recipes
const RecipeStack = StackNav({ [RECIPES]: Recipes });
// Screens for Restaurants
const RestaurantStack = StackNav({ [RESTAURANTS]: Restaurants });
// Tab container
const Root = createAppContainer(
createBottomTabNavigator(
{
RECIPE: RecipeStack,
RESTAURANT: RestaurantStack,
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarComponent: ({ tintColor }) => (
<TabBarComponent tintColor={tintColor} navigation={navigation} />
),
}),
tabBarOptions: {
activeTintColor: 'tomato',
inactiveTintColor: 'gray',
showIcon: true,
},
},
),
);
export default Root;
I am not sure what am I doing wrong. I tried digging through the official documentation. I couldn't find anything worth resolving this issue.
Define navigationOptions with the tabBarIcon inside.
You can return the image via the tabBarIcon.
The tabBarIcon receives a function with the several arguments and returns a component. In your case, an Image.
Try this in your tab container.
tabBarIcon: ({ tintColor }) => {
return (<Image style={styles.iconStyle} source={IMAGE_PATH[routeName]} />);}
}

Where should propTypes for navigationOptions inside createBottomTabNavigator be defined?

I just applied airbnb, prettier, react/prettier and all the linting. I still cannot get around this error[1] because I do not understand correctly where should propTypes should be declared for "inner functions" as this one.
I am not giving those parameters, nor am I defining them. They come from createBottomTabNavigator as I can read in the doc. So, how should I have a say in what props are required for tabBarIcon and which are not in the destructuring of these?[2]
UPDATE
[1] The error is a linting error. The code executes just fine.
[2] We can of course fiddle around a bit to get it working and avoid the errors but my aim is to understand how it works and why is it giving the errors back, being that snippet the official example.
export const HomeStackNavigator = createStackNavigator(
...
);
export const TabNavigator = createBottomTabNavigator(
{
Home: {
screen: HomeStackNavigator,
},
Settings: {
screen: SettingsStackNavigator,
},
},
{
initialRouteName: 'Home',
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
// Getting error here ^^^^^^^^^^^^^^^^^^^^^^^^
// 'focused' is missing in props validationeslint(react/prop-types)
const { routeName } = navigation.state;
let IconComponent = Ionicons;
let iconName;
if (routeName === 'Home') {
iconName = 'ios-home';
IconComponent = HomeIconWithBadge;
} else if (routeName === 'Settings') {
iconName = 'ios-cog';
}
return (
<IconComponent //
name={iconName}
color={tintColor}
size={25}
/>
);
},
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}),
tabBarOptions: {
// activeTintColor: 'tomato',
keyboardHidesTabBar: true,
},
}
);
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
appState: AppState.currentState,
isStoreLoading: false,
store: createStore(rootReducer)
};
}
...
componentDidMount(){...}
...
render() {
const { store, isStoreLoading } = this.state;
if (isStoreLoading) {
return <Text>Loading...</Text>;
}
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
If you really want to define prop-types for an inner function like this, you need to move it outside of the navigator.
const MyTabBarIcon = ({ focused, horizontal, tintColor, navigation }) => {
// [...]
}
MyTabBarIcon.propTypes = {
focused: PropTypes.bool.isRequired,
tintColor: PropTypes.string.isRequired,
navigation: PropTypes.object.isRequired,
horizontal: PropTypes.bool,
}
Then your TabNavigator becomes:
// [...]
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: props => <MyTabBarIcon {...props} navigation={navigation} />,
// [...]
});
// [...]

Why does the header icon load slowly in react-native?

I'm using both Stack Navigations and Draw Navigations at the same time.
"react-navigation": "3.0.4"
My App.js
import React, { Component } from "react";
import HomeScreen from "./screen/HomeScreen";
export default class AwesomeApp extends Component {
constructor() {
super();
this.state = {
isReady: false
};
}
async componentWillMount() {
await Expo.Font.loadAsync({
Roboto: require("native-base/Fonts/Roboto.ttf"),
Roboto_medium: require("native-base/Fonts/Roboto_medium.ttf"),
Ionicons: require("native-base/Fonts/Ionicons.ttf")
});
this.setState({ isReady: true });
}
render() {
if (!this.state.isReady) {
return <Expo.AppLoading />;
}
return <HomeScreen />;
}
}
my HomeScreen:
import React, { Component } from "react";
import TestScreen from "../TestScreen";
...
import {
createDrawerNavigator,
createAppContainer,
createStackNavigator
} from "react-navigation";
import { AsyncStorage } from "react-native";
import Expo, { Constants, LocalAuthentication } from "expo";
import TouchID from "react-native-touch-id";
import crypto from "crypto";
import safeCrypto from "react-native-fast-crypto";
import { asyncRandomBytes } from "react-native-secure-randombytes";
const defaultNavigationOptions = {
headerTintColor: "black",
headerStyle: {
borderBottomColor: "#fff",
borderBottomWidth: 1,
shadowColor: "transparent",
elevation: 0
},
headerTitleStyle: {
fontWeight: "bold",
fontSize: 18
}
};
window.randomBytes = asyncRandomBytes;
window.scryptsy = safeCrypto.scrypt;
let pinnumbercheck = AsyncStorage.getItem("pinnumber");
let powersucesscheck = AsyncStorage.getItem("powersucess");
let nicknamecheck = AsyncStorage.getItem("nickname");
let compatible = Expo.LocalAuthentication.hasHardwareAsync();
let fingerprints = Expo.LocalAuthentication.isEnrolledAsync();
const Drawers = createDrawerNavigator(
{
FirstAgree: {
screen: UserAcceptanceScreen
},
Test: { screen: TestScreen }
},
{
initialRouteName: "FirstAgree",
contentComponent: props => <SideBar {...props} />
}
);
const SettingStack = createStackNavigator(
{
screen: SettingScreen
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const FirstAgreeStack = createStackNavigator(
{
screen: UserAcceptanceScreen
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const FirstAgreeStack2 = createStackNavigator(
{
screen: UserAcceptanceScreen2
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const WalletScreenStack = createStackNavigator(
{
screen: RegisterWalletScreen2
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const WalletScreen2Stack = createStackNavigator(
{
screen: RegisterWalletScreen3
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const TakeWalletStack = createStackNavigator(
{
screen: TakeWalletScreen
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const RegisterSecurityStack = createStackNavigator(
{
screen: RegisterSecurityScreen
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const RegisterSecurityStack2 = createStackNavigator(
{
screen: RegisterSecurityScreen2
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const PinNumberLoginStack = createStackNavigator(
{
screen: PinNumberLogin
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const TestssStack = createStackNavigator(
{
screen: Testss
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const NickRegisterStack = createStackNavigator(
{
screen: NickRegisterScreen
},
{
defaultNavigationOptions,
headerLayoutPreset: "center"
}
);
const stackScreen = createStackNavigator(
{
Drawers: {
screen: Drawers
},
UserRight: {
screen: UserRightScreen
},
...(very more)
},
{
initialRouteName: "RegisterWalletIndex",
headerMode: "none"
}
);
const HomeScreenRouter = createAppContainer(stackScreen);
export default HomeScreenRouter;
There's nothing wrong with moving between screens and how to use them.
However, the header icon is displayed too late on the next screen when you move the screen.
The header icon is displayed later than the full screen. So you can't act on the screen right away.
usepage.js:
import React from "react";
import {
View,
Text,
TouchableOpacity,
Alert,
Image,
Platform,
ActivityIndicator
} from "react-native";
import { ListItem, CheckBox, Body } from "native-base";
import styles from "./styles.js";
import { Ionicons } from "#expo/vector-icons";
import { NavigationActions } from "react-navigation";
class UserAcceptanceScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
allCheckbox: false,
checkbox1: false,
checkbox2: false,
checkbox3: false,
loading: false
};
}
static navigationOptions = ({ navigation }) => {
return {
headerLeft: (
<TouchableOpacity
style={{ paddingLeft: 15 }}
onPress={() => navigation.dispatch(NavigationActions.back())}
>
<Ionicons name={"ios-arrow-back"} size={35} color={"black"} />
</TouchableOpacity>
)
};
};
componentDidMount() {
this.setState({
loading: true
});
}
allToggleSwitch() {
this.setState({
allCheckbox: !this.state.allCheckbox
});
}
toggleSwitch1() {
this.setState({
checkbox1: !this.state.checkbox1
});
}
toggleSwitch2() {
this.setState({
checkbox2: !this.state.checkbox2
});
}
toggleSwitch3() {
this.setState({
checkbox3: !this.state.checkbox3
});
}
render() {
return this.state.loading === false ? (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<ActivityIndicator size="large" />
</View>
) : (
<View style={styles.container}>
...
</View>
);
}
}
export default UserAcceptanceScreen;
I don't know why because I did what I wanted. Is there something I'm missing?
Please give us a lot of feedback and help.
I didn't know why it was loading slowly, but I solved this problem by designing one loading screen.
My use loading Screen Page:
import React, { Component } from "react";
import {
AsyncStorage,
Text,
View,
ActivityIndicator,
TouchableOpacity
} from "react-native";
import { Ionicons } from "#expo/vector-icons";
class StartScreen extends Component {
constructor(props) {
super(props);
this.state = {
isReady: false
};
}
static navigationOptions = ({ navigation }) => {
return {
headerLeft: (
<TouchableOpacity
style={{ paddingLeft: 15 }}
onPress={() => navigation.navigate("FirstAgreeStack")}
>
<Ionicons name={"ios-arrow-back"} size={35} color={"#ffffff"} />
</TouchableOpacity>
),
headerRight: null
};
};
async componentWillMount() {
let powersucess = await AsyncStorage.getItem("powersucess");
let keystoredata = await AsyncStorage.getItem("keystoredata");
let nickname = await AsyncStorage.getItem("nickname");
let pinnumber = await AsyncStorage.getItem("pinnumber");
let useTouchId = await AsyncStorage.getItem("useTouchId");
powersucess === null
? this.props.navigation.navigate("CusterMizingAlert")
: keystoredata === null
? this.props.navigation.navigate("RegisterWalletIndex")
: nickname === null
? this.props.navigation.navigate("RegisterWalletIndex")
: pinnumber === null
? this.props.navigation.navigate("RegisterSecurity")
: pinnumber === null
? this.props.navigation.navigate("RealPinNumberLogin")
: useTouchId === "useTouchId"
? this.props.navigation.navigate("TouchIdLogin")
: this.props.navigation.navigate("FaceIdLogin"),
this.setState({ isReady: true });
}
render() {
return this.state.isReady === false ? (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<ActivityIndicator size="large" />
</View>
) : (
<View>
<Text>dfdff</Text>
</View>
);
}
}
export default StartScreen;

double header stack navigation react-native

I am using a StackNavigator in react native app.
The problem is that in my app, it creates two headers ...
I would like to keep the upper one to go back from the screen. Is it possible without recreate the back button manually ?
Screen:
class CommandsList extends React.Component {
constructor(props){
super(props);
}
addCommand = () => {
this.props.navigation.navigate("CreateCommand");
}
render() {
return (
<SafeAreaView style={{flex:1}}>
<MyList itemsUrl="http://localhost:9000/commands"/>
<Button title="Ajouter" onPress={this.addCommand}></Button>
</SafeAreaView>
);
}
}
export default StackNavigator({
CommandsList : {
screen : CommandsList,
},
});
EDIT :
App.js
const navigationOptions = ({ navigation }) => ({headerLeft: <Icon name {'chevron-left'} onPress={ () => { navigation.goBack() }} />})
const RootStack = StackNavigator(
{
CommandsList: {
screen: CommandsList,
},
CreateCommand: {
screen: CreateCommand,
},
ListFournisseurs: {
screen: ListFournisseurs,
},
ListAffaires: {
screen: ListAffaires,
}
},
{
initialRouteName: 'CommandsList',
headerMode:'none',
navigationOptions:{navigationOptions}
}
);
According to the docs, if you want to disable the header of the StackNavigator, you can apply the config at your StackNavigatorConfig as headerMode: 'none'. It is back propagated from Child to Parent, if Parent is none then Child will also not be rendered.
Therefore for a single header, in your case you should do
export default StackNavigator({
CommandsList : {
screen : CommandsList,
},
}, {
headerMode: 'none'
});
For the back button in the Parent Stack, you can create a component as
const navigationOptions = ({ navigation }) => ({
headerLeft: <Icon name={'arrow-left'} //<== Vector Icon Here
onPress={ () => { navigation.goBack() }} />
const RootStack = StackNavigator(
RouteConfigs,
//... StackNavigatorConfigs,
navigationOptions
)

States in TabStackNavigator?

It seems like TabNavigator doesn't have it's own state. Is there any way to use state or props?
I want to show the number of unread notification on the Notification TabIcon.
export default TabNavigator(
{
...
Noti: {
screen: NotificationStackNavigator,
},
...
},
{
navigationOptions: ({ navigation }) => ({
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
let iconName;
switch (routeName) {
...
case 'Noti':
iconName = 'ios-notifications';
break;
...
}
...
if(iconName == 'ios-notifications') {
return (
<IconBadge
MainElement={
<Ionicons
name={iconName}
size={30}
style={{ marginBottom: -3 }}
color={focused ? Colors.tabIconSelected : Colors.tabIconDefault}/>
}
BadgeElement={
<Text style={{color:'#FFFFFF'}}>
{{this.state.notifications}} // my goal
</Text>
}
IconBadgeStyle={{
backgroundColor: '#f64e59',
position: 'absolute',
right:-10,
top:0,
}}
/>
);
}
...
Please let me know if anything is unclear. Thanks in advance
UPDATE I'm planning to refactor my TabNavigator. Is this what you're trying to say?
export default TabNavigator(
to
const MainTabNavigator = TabNavigator(
class MainTabNavigator extends Component {
state = {notification}
export default connect(...)(MainTabNavigator);
UPDATE 2 MainTabNavigator's Top Level component is another TabNavigator. Is there any other solution?
const RootTabNavigator = TabNavigator ({
Auth: {
screen: AuthStackNavigator,
},
Welcome: {
screen: WelcomeScreen,
},
Main: {
screen: MainTabNavigator,
},
You can pass additional custom props via screenprops
const TabNav = TabNavigator({
// config
});
<TabNav
screenProps={/* this prop will get passed to the screen components as this.props.screenProps */}
/>
The full documentation is here

Resources