Skip to content

How can I selectively hide Tabs with nested StackNavigators on certain screens? #464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
KabirKang-zz opened this issue Feb 23, 2017 · 62 comments
Labels

Comments

@KabirKang-zz
Copy link

I have a TabNavigator with three TabView children. Each TabView has a stacknavigator as a child so that the user can visit detail views, etc. I want to hide the tabs on one of the detail views. I have tried registering each navigator in a high level context and then accessing the tab navigator from the screen and doing setParams on it, but it doesn't seem that setParams is doing anything.

What is the best way to selectively hide the tabs?

@KabirKang-zz
Copy link
Author

The tabs:

this.TabNav = TabNavigator( HOME_TABS_ROUTER, { initialRouteName: this.initialTab, tabBarPosition: 'bottom', tabBarComponent: TabView.TabBarBottom, order: [HOME_TABS.FIND_TEACHER_TAB, HOME_TABS.CHAT_TAB, HOME_TABS.DASHBOARD_TAB], lazyLoad: true, swipeEnabled: false, animationEnabled: false, tabBarOptions: { showIcon: true, style: { backgroundColor: Colors.headerBackground, height: normalizeValue(65), padding: normalizeValue(10) }, labelStyle: textStyles.footerLabel, activeTintColor: Colors.brandDefault, inactiveTintColor: Colors.defaultText } } );

A child stack nav:

this.StackNavigator = Router(
{
headerComponent: CustomHeader,
headerMode: 'screen',
initialRouteName: ROUTES.FIND_TEACHER_TAB,
navigationBarOptions: {
header: {
title: () => i18n.t('home.chatHeaderTitle'),
}
}
}
);

@grabbou
Copy link

grabbou commented Feb 23, 2017

In order to hide a tabBar on one of the tabs, you can set this option to true. It's been recently documented. Is that something you were looking for?

@KabirKang-zz
Copy link
Author

@grabbou but how would you put the tabBar navigation option on a screen that is a part of the stacknavigator.

the stack nav is a child of the tabnavigators. that's why i was trying to find a round-about way to reach the "grandparent" navigator.

@KabirKang-zz
Copy link
Author

and i cant make the profile screen part of the tabnavigator because that would make it a tab. not really what i want. let me know if my question is too confusing, and i'll try to add more code.

@grabbou
Copy link

grabbou commented Feb 23, 2017

I think I know what's the issue. I have one idea I'll try to send you tomorrow morning, maybe it works.

@KabirKang-zz
Copy link
Author

ok thanks

@KabirKang-zz
Copy link
Author

@grabbou does it involve redux?

@dbhowell
Copy link

@KabirKang, to do this you have to have the Parent navigation as the StackNavigation, and then have your TabNavigation as one of the Stack screens. Then by turning on/off the visible of each of the top Stack screens you get your desired outcome.

import ContactView from './ContactView'; // Detail view of a contact
import TabNavigation from './TabNavigation'; // Child TabNavigation that would call navigate('Contact', {contactId: 1});
import { StackNavigator } from 'react-navigation';

const TopNavigation = StackNavigator({
  Home: {
    screen: TabNavigation
  },
  Contact: {
    screen: ContactView,
    navigationOptions: {
      header: {
        visible: true
      }
    }
  }
}, {
  navigationOptions: {
    header: {
      visible: false
    }
  }
});

export default TopNavigation;

@KabirKang-zz
Copy link
Author

@dbhowell In your example you wrote header: {visible: true}. Did you mean tabBar? and would that work for tabbar?

@dbhowell
Copy link

dbhowell commented Feb 23, 2017

Note that { visible: true} in this case hides the Stack toolbar, not the Tabbar.

Edit: I meant that {visible true} shows the Stack toolbar

@KabirKang-zz
Copy link
Author

KabirKang-zz commented Feb 23, 2017

@dbhowell So would the structure you suggest allow you to visit more screens within each tab or would each tab still need to have a nested stack navigator in each?

Also what does this do to the back button? If you were to switch tabs, and then click the Android back button, would it go back to the previous tab?

@dbhowell
Copy link

It does get confusing, so the best thing is to try it out and see what the result is.

@bjrn
Copy link

bjrn commented Feb 28, 2017

@dbhowell that works fine with headers (if headerMode: "screen"), but that's probably because the header option is part of the stackNavigator.

tabBar: { visible: false } won't work since it's part of the parent TabNavigator.

I'm also interested in how to do this, as a typical use case for us would be to hide the tab bar on the second level navigation throughout the app.

Would be willing to help out implementing it, but need a few pointers on how to go about it. @grabbou, any ideas you have would be highly appreciated!

@ahmedlhanafy
Copy link

@grabbou any update on this one?

@jamesone
Copy link

jamesone commented Mar 2, 2017

I think there should be an API which we can configure - This could update the global state of the navbar where it's configured - E.g inside a stack navigator:

static tabbarOptions = {
    hide: true,
    ...Otheroptions
}

However I'm not sure how react-navigation is setup internally.

@Kerumen
Copy link

Kerumen commented Mar 4, 2017

I faced this problem too today. I need to hide the tabBar in the second level of navigation in one tab.

I have a TabNavigator with StackNavigators in each tab. For one tab I'd like to hide the tabBar if I'm in in a deep screen of the StackNavigator.

Is it possible right now with a certain config?
The idea is to "bubble up" the deep child screen navigationOptions config to the top TabNavigator parent.

@satya164
Copy link
Member

satya164 commented Mar 4, 2017

Create separate tab navigators with the config you want and use them.

@Kerumen
Copy link

Kerumen commented Mar 4, 2017

Do you have an example? I can't figure out how to do this.
Currently I have this config:

TabNavigator({
  Screen1:  { screen: StackNavigator({...}) }, 
  Screen2:  { screen: StackNavigator({...}) }, 
  Screen3:  { screen: StackNavigator({
    SubScreen1: { screen: ...  }, 
    SubScreen2: { screen: ...  }, 
  }) }, 
})

I want to hide the tabBar on the SubScreen2.

@adogor
Copy link

adogor commented Mar 17, 2017

Hi,

I found a simple solution.

if we take @Kerumen config, we can add a per tab navigationOptions (has I think you've already done) :

TabNavigator({
    Screen1: { screen: StackNavigator({ ...}) },
    Screen2: { screen: StackNavigator({ ...}) },
    Screen3: {
        screen: StackNavigator({
            SubScreen1: { screen: ...  },
            SubScreen2: { screen: ...  },
        }),
        navigationOptions: {
            title: 'Your title',
            tabBar: (state, acc) => {
                return {
                    label: '',
                    icon: ...,
                    visible: (acc && acc.visible !== 'undefined') ? acc.visible : true,
                }
            },
        }
    },
})

The main thing there is that the tabBar option is a function that take 2 parameters : state and acc, acc is the tabBar config from the current Screen (well it's a little more complex than that). You just have to merge that.

And for example in SubScreen2 you can have this :

static navigationOptions = {
    header: { ...},
    tabBar: {
        visible: false,
    },
}

Hope it helps ;-)

@bramvbilsen
Copy link

Hi,
I am still struggling with this. I've basically got a stackNavigator and want to make a tabNavigator a parent of the stackNavigator. The stackNavigator got 2 screens and I only want to show on one of those the navbar. This is what my code looks like:

` export const MainStack = StackNavigator({
Home: {
screen: Home,
navigationOptions: {
title: "Home"
}
},
AdventureArticle: {
screen: AdventureArticle,
navigationOptions: {
title: "Adventure"
}
}
})

export const MainTabs = TabNavigator(
{
MainStack: {
screen: MainStack, // Replaced Feed with FeedScreen
navigationOptions: {
tabBar: {
label: 'Home',
icon: ({ tintColor }) => <Icon name={"google-plus-square"} size={35} color={tintColor} />,
showIcon: true
},
},
},
},
{
tabBarPosition: "bottom"
}
);`

I tried putting this in the code of my "AdventureArticle" screen (in my stackNavigator):
static navigationOptions = { tabBar: () => ({ visible: false, label: "test" }), };
But as I read on this topic, it doesn't work.

Is there a simple and straightforward way of handling this?

@adogor
Copy link

adogor commented Mar 19, 2017

@bramvbilsen

Your config in AdventureArticle is overridden by the navigationOptions of the TabNavigator. Try this :

export const MainTabs = TabNavigator(
    {
        MainStack: {
            screen: MainStack, // Replaced Feed with FeedScreen
            navigationOptions: {
                tabBar: (state, acc) => {
                    return {
                        label: (acc && acc.label !== 'undefined') ? acc.label : 'Home',
                        icon: ({ tintColor }) => <Icon name={"google-plus-square"} size={35} color={tintColor} />,
                        showIcon: true,
                        visible: (acc && acc.visible !== 'undefined') ? acc.visible : true,
                    };
                },
            },
        },
    },
    {
        tabBarPosition: "bottom"
    }
);

@bramvbilsen
Copy link

@adogor This kinda worked to just hide the tabbar, but the user is still able to swipe left or right to go to the other scenes in my tabbar. Is there a way to lock it on a particular screen?

@Kerumen
Copy link

Kerumen commented Mar 20, 2017

@bramvbilsen You can add swipeEnabled: falseto prevent the swipe. (docs)

@bramvbilsen
Copy link

@Kerumen But how would I do it for a single page that is a child of a StackNavigator?

@jamesone
Copy link

The way we did it was:

const NoTabScreens = {
  NameOfScreen: {
    screen: Component
  },
  NameOfScreen2: {
    screen: Component2
  }
};

const MyApp = TabNavigator({ ...options ... });
const mainStackNavigator = {
  // MyApp is a TABNAVIGATOR with 3 tabs
  Main: {
    screen: MyApp,
    navigationOptions: {
      header: { visible: false },
    },
  },
  // The following screens won't have tabs
  // Place any screens that shouldn't have a tab in here
  ...NoTabScreens,
};

const StackApp = StackNavigator(mainStackNavigator, {
  headerMode: 'screen',
  initialRouteName: 'Main', // on startup, goto main tabnav
  navigationOptions: {
   ... Add your global nav options here ...
  },
});

This works fine!

We then store the top level navigation object in redux so we can route to these screens from within nested tabnav/stacknavs.

E.g globalNavigation.navigate('NameOfScreen')

@daweiba12
Copy link

You can bind the tabBarVisible to a params, and on the screens that you want to hide the tab bar, fire a NavigationActions.setParams action to set the value of the parameter to the specific tab route.

@twairball
Copy link

I have this working as follows:

const MessagesNav = StackNavigator({
    Messages: { screen: Messages },
    MessageDetail: { 
        screen: MessageDetail,
        navigationOptions: ({ navigation }) => ({
             title: 'Message',
             tabBarVisible: false, 
        )}
    },
})

const Tabs = TabBarNavigator({
    MessagesNav: { screen: MessagesNav },
    SettingsNav: { screen: SettingsNav },
})

class App extends Component {
    render() {
        return(<Tabs/>)
    }
}

npm versions:

react: 16.0.0-alpha.6
react-native: 0.44.3

@fengerzh
Copy link

@twairball confirmed, that works!

@gulshanzealous
Copy link

@twairball That works dude. Thanks !

@twairball
Copy link

twairball commented Jul 5, 2017

I'd be happy to make a PR to docs for this example. Where should I put this?

Now, if only anyone can show me how to hide tabs dynamically :)

@EdmundMai
Copy link

nice @twairball ! I noticed one detail: the StackNavigator component must be the tab itself. If you have anything inbetween like a container component, this will not work.

@yogeshwar-20
Copy link

I have this root navigator:

import HomeNavigator from './HomeNavigator';

export default {
  Splash: { screen: SplashContainer },
  Activation: { screen: ActivationContainer },
  Login: { screen: LoginContainer },
  ForgotPassword: { screen: ForgotPasswordContainer },
  Register: { screen: RegisterContainer },
  HomeScreen: { screen: HomeNavigator },
};

HomeNavigator:

@connect(store => ({ authentication: store.authentication }))
class HomeNavigator extends Component {
  getRoutesConfig = () => {
    const tabToComponentMap = {
      Screen1: Screen1Navigator,
      Screen2: Screen2Navigator,
      ...
    };

    const config = {};
    const { tabs = [] } = this.props.authentication.loginDetails;

    tabs.forEach(tab => (config[tab] = { screen: tabToComponentMap[tab] }));

    return config;
  };

  render() {
    const config = this.getRoutesConfig();

    const Navigator = Platform.select({
      ios: () => TabNavigator(config),
      android: () => DrawerNavigator(config),
    })();

    return <Navigator />;
  }
}

Every screen in the Tab/Drawer navigator is in turn a StackNavigator (Screen1Navigator, etc.). On some of those screens inside that StackNavigator i need to hide the tab bar. Currently i'm achieving the behaviour i need by passing down functions as screenProps from Screen1Navigator to its screens to alter the navigationOptions on Screen1Navigator itself, through navigation.setParams. This seems pretty hacky, and also adding tabBarVisible: false on any screen of Screen1Navigator itself doesn't affect the TabNavigator.

Also to add a button in the header to toggle the Drawer for Android, i'm having to pass down another function from Screen1Navigator that dispatches navigate('DrawerOpen'). Is this the only way to achieve this behaviour? Considering my root navigator is integrated with redux, i know i'm missing something with the conditionally nested Tab/Drawer navigator. Pretty sure it has something to do with me mounting the navigator myself with <Navigator />. I tried passing navigation={this.props.navigation} to it myself, but it made the nested routes disappear. How would i share the router and navigation state with children in this scenario? 😟

@aroth
Copy link

aroth commented Jul 26, 2017

As @EdmundMai mentioned:

the StackNavigator component must be the tab itself. If you have anything inbetween like a container component, this will not work.

Setting tabBarVisible: false on individual screens worked great until I started wrapping the StackNavigator inside of a component.

// Setting tabBarVisible: false inside of DetailScreen

//
// This works:
//

const Navigator = StackNavigator(
  {
    Directory: { screen: DirectoryScreen },
    Detail: { screen: DetailScreen },
  },
  {
    navigationOptions: ({ navigation }) => ({ ...defaultHeaderOptions }),
    initialRouteName: 'Directory',
  },
)

export default Navigator;

//
// This doesn't:
//


class DirectoryNavigator extends React.Component {
  render() {
    const Navigator = StackNavigator(
      {
        Directory: { screen: DirectoryScreen },
        Detail: { screen: DetailScreen },
      },
      {
        navigationOptions: ({ navigation }) => ({ ...defaultHeaderOptions }),
        initialRouteName: 'Directory',
      },
    );
    return (
      <Navigator
        onNavigationStateChange={(prevState, currentState) => {
          trackScreenOnNavigationStateChange({ prevState, currentState });
        }}
      />
    );
  }
}

export default DirectoryNavigator;

TabView does not re-render when navigators are wrapped components.

@alxvallejo
Copy link

@aroth - Thanks for that tip. Was def. scratching my head for a while.

@spencercarli
Copy link
Member

Hi! In an effort to get the hundreds of issues on this repo under control I'm closing issues tagged as questions. Please don't take this personally - it's simply something we need to do to get the issues to a manageable point. If you still have a question I encourage you to re-read the docs, ask on StackOverflow, or ask on the #react-navigation Reactiflux channel. Thank you!

@rvlewerissa
Copy link

Hi, I got the same issue, setting tabBarVisible on individual screens won't work, anyway I was connecting each stack to a container first because I'm using Redux to store the navigation state.

NavigatorTabBar --> Container --> NavigatorStack --> Screen

@amitava82
Copy link

@aroth Is there a solution to that?

@SimonVuong
Copy link

SimonVuong commented Oct 24, 2017

@amitava82 @rvlewerissa @aroth

I was able to get this working with a redux container by wrapping the navigation view with redux instead of the actual navigator. I copied most of the config from

https://github.com/react-community/react-navigation/blob/master/src/navigators/StackNavigator.js

AccountNavigator.js

import React from 'react';
import { connect } from 'react-redux';
import { StackNavigator, addNavigationHelpers, StackRouter, createNavigator, CardStackTransitioner } from "react-navigation";
import AccountScreen from './AccountScreen';
import SignInScreen from './SignInScreen'

const StackView = ({ dispatch, accountNav, ...extraProps }) => (
  <CardStackTransitioner {...extraProps} navigation={addNavigationHelpers({ dispatch, state: accountNav })} />
)

const mapStateToProps = ({accountNav}) => ({
  accountNav,
});

const StackViewWithRedux = connect(mapStateToProps)(StackView);

const AccountNavigator = (props) => {
    
  const router = StackRouter({
    accountScreen: { screen: AccountScreen },
    signInScreen: { screen: SignInScreen }
  });

  return createNavigator(router)(StackViewWithRedux);
}

export default AccountNavigator();

@rvlewerissa
Copy link

@SimonVuong and you're using the tabBarVisible on the individual screen?

@SimonVuong
Copy link

@rvlewerissa
yes

@walterjar
Copy link

walterjar commented Oct 31, 2017

The same question, I want to hide the tab bar by click a buttom dynamicly.
The entry page is a TabNavigator, it contains two screens, and the one is HideTabScreen.
When I click the hide_button in HideTabScreen, the two tabs should be hidden by change tabBarVisible's value.
But that doesn't work ! Anyone can show me how to hide tabs dynamically? :)

_________________
|                |
|                |
| HideTabScreen  |
| hide_button    |
|                |
|________________|
|__TabA__|_TabB__|

     ->>>
_________________
|                |
|                |
| HideTabScreen  |
| show_button    |
|                |
|________________|

I reference @weili1977 soulution, #464

This is my code structure (not exact).


// Entry TabNavigator with a Wrapper
MyTabNavigator = TabNavigator({
  TabA: {
    screen: HideTabScreen,
    navigationOptions: ({navigation, screenProps}) => ({
    	  tabBarLabel: 'HideTabScreen',
			tabBarVisible: (screenProps.topNavigation.state.params != undefined) ? (screenProps.topNavigation.state.params.bottomBarVisible != null ?  screenProps.topNavigation.state.params.bottomBarVisible: false) : true
    }),
  },
  TabB: ...
}
);

class MyTabNavigatorWrapper extends Component {
  render() {
	return(<MyTabNavigator screenProps={{topNavigation: this.props.navigation }} ></MyTabNavigator>)
  }
};

const entryStack = StackNavigator({
  ScreenOne: {
    screen: MyTabNavigatorWrapper,
  }
});

AppRegistry.registerComponent('app', () => entryStack);

// HideTabScreen
hideTest = () => {
	const setParamsAction = NavigationActions.setParams({
		params: { bottomBarVisible: false }
	});
	this.props.screenProps.topNavigation.dispatch(setParamsAction);
	// navigate('OtherPage');
}

render() {
	return	<View><Button 
			onPress={ () => setTimeout( () =>this.hideTest(), 150) } 
			title="hide_button"
		/>
	</View>
}


@zhangyu921
Copy link

It seems the false always override true

const Stack1 = StackNavigator(
  {
    stackScreen1: { screen: StackScreenG('Go Next 2', 'stackScreen2') },
    stackScreen2: {
      screen: StackScreenG('Go Next 3', 'stackScreen3'),
      navigationOptions: ({ navigation }) => ({
        title: 'Message',
        tabBarVisible: true,
      }),
    },
    stackScreen3: { screen: StackScreenG('Go Next 1', 'Screen1') },
  },
  {
    tabBarVisible: false,
  }
)

I can't set a first screen shown tabBar in this config.

@dounine
Copy link

dounine commented Dec 10, 2017

I wrote four ways to achieve, we can refer to is not what you want, the demo is as follows

projeect url navigationChat
demo show

@s-low
Copy link

s-low commented Dec 12, 2017

@EdmundMai and anybody else trying to use tabBarVisible: false with a wrapping / container component around the nested navigator.

The following appears to work (modified from @twairball's example) using the comment at the bottom of this page in the docs: https://reactnavigation.org/docs/intro/nesting

The most important difference is to explicitly pass the navigation prop through the wrapper into the nested navigator, and to manually assign wrapper.router to nestedNavigator.router

const MessagesNav = StackNavigator({
    Messages: { screen: Messages },
    MessageDetail: {
        screen: MessageDetail,
        navigationOptions: {
             tabBarVisible: false
        }
    },
})

class MessagesNavWrapper extends React.Component {
    constructor(props){
        super(props)
        console.log('This is the wrapper component!')    
    }

    render(){
        return <MessagesNav navigation={this.props.navigation}>
    }
}

// IMPORTANT
MessagesNavWrapper.router = MessagesNav.router

const Tabs = TabBarNavigator({
    MessagesNav: { screen: MessagesNavWrapper },
    SettingsNav: { screen: SettingsNav },
})

class App extends Component {
    render() {
        return(<Tabs/>)
    }
}

@ScreamZ
Copy link

ScreamZ commented Dec 18, 2017

I love @twairball solution but what if i have the following structure ?

class JournalNavigation extends React.Component {
  static navigationOptions = nav => {
    return {
      title: i18n.t('navigation.journal').toUpperCase(),
      tabBarIcon: ({ focused }) => (
        <TabBarIcon icon="journal" focused={focused} />
      ),
      tabBarVisible: true // I want to change the value here thanks to my JournalOnboardingNavigation currentScreen option value.
    }
  }

  static propTypes = {
    hasCompleteOnboarding: PropTypes.bool.isRequired
  }

  render() {
    return this.props.hasCompleteOnboarding ? (
      <JournalReadyNavigation />
    ) : (
      <JournalOnboardingNavigation />
    )
  }
}

const mapStateToProps = state => ({
  hasCompleteOnboarding: hasCompleteOnboarding(state)
})
export default connect(mapStateToProps)(JournalNavigation)
const JournalOnboardingNavigation = StackNavigator(
  {
    JournalOnboarding: { screen: JournalOnboarding },
    TrackersSelectionScreen: {screen: TrackersSelectionScreen}
  },
  {
    cardStyle: {
      // StackNavigator seems to add a black background behind
      backgroundColor: 'white'
    }
  }
)

I've no idea how i can propagate to the third level....

Any idea ?

Best regards

@gagamil
Copy link

gagamil commented Feb 1, 2018

If you need to hide the parent TabBar in a specific StackNavigator Screen then this would work:

class MyComponent extends Component{
  static navigationOptions = {
    tabBarVisible: false,
  };
  render(){
    return(
      //your screen components here
    );
  }
}

@MrLoh
Copy link
Contributor

MrLoh commented Apr 13, 2018

It seems like this functionality was removed in v2-rc1

@DenysYakubets
Copy link

DenysYakubets commented Jun 11, 2018

Hi there! I found answer here: https://reactnavigation.org/docs/en/navigation-options-resolution.html

FeedStack.navigationOptions = ({ navigation }) => {
  let tabBarVisible = true;
  if (navigation.state.index > 0) {
    tabBarVisible = false;
  }

  return {
    tabBarVisible,
  };
};

@raideltorres
Copy link

I solved this by simply doing . . .

const MainNav = TabNavigator({
    Home: {
        screen: HomeRoutes,
    }
},{
    initialRouteName: 'Home',
    tabBarPosition: 'bottom',
});

and in the child route

const HomeRoutes = StackNavigator({
    Home: {screen: Home},
    Splash: {
        screen: Splash,
        navigationOptions: {
            tabBarVisible: false
        },
    },
    Login: {screen: Login},
    SignUp: {screen: SignUp}
},{
    initialRouteName: 'Splash'
});

Nothing else is needed

@maheshsgr
Copy link

https://reactnavigation.org/docs/en/navigation-options-resolution.html#a-tab-navigator-contains-a-stack-and-you-want-to-hide-the-tab-bar-on-specific-screens

@MadavaramRamesh
Copy link

@Kerumen Hi, i want to hide TabBar when right bar button(like cart screen which is common for every tab) on navigation bar clicked.tabBarVisible = true is not working for this case

@SuarezLuis
Copy link

For bottom tab navigators, I made a npm package for that, check :
https://www.npmjs.com/package/react-navigation-selective-tab-bar

@3rdp
Copy link

3rdp commented Oct 23, 2019

The downsides of this https://reactnavigation.org/docs/en/navigation-options-resolution.html#a-tab-navigator-contains-a-stack-and-you-want-to-hide-the-tab-bar-on-specific-screens is not smooth transition animation when you hide/show TabBar. To make the animations look smoother you need to make TabNavigator a child of StackNavigator and then screens from Stack will render above the TabBar.

A good idea would be to animate the TabBar as well to make it look fine, but I haven't implemented that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests