react-navigation / react-navigation

Routing and navigation for your React Native apps
https://reactnavigation.org
23.33k stars 4.98k forks source link

Can I hide tab bar dynamically? #888

Closed zhaoyi0113 closed 6 years ago

zhaoyi0113 commented 7 years ago

Below is my code on a tab bar component.

static navigationOptions = {
    tabBar: {
      label: '',
      icon: (obj) => {
        const image = obj.focused ? require('1.png') : require('2.png');
        return <Image style={{resizeMode:'contain', width:40, height: 40}}
                      source={image}
        />
      }
    },
  };

Can I hide the tab bar dynamically? I know I can add visible: false under tabBar property but I want to show/hide based on a component state. How can I achieve this?

Psiiirus commented 7 years ago

Have you tried to use setParams?

https://reactnavigation.org/docs/navigators/navigation-prop#setParams-Make-changes-to-route-params

zhaoyi0113 commented 7 years ago

I tried this code this.props.navigation.setParams({tabBar:{visible:false}}) but it doesn't make the tab bar invisible? Did I pass a wrong parameter?

zhaoyi0113 commented 7 years ago

I think setParams only works for setting route parameters. But the visible:false is not a route parameter. Instead it is a configuration set under navigationOptions. Is there a way to update it dynamically?

Psiiirus commented 7 years ago

mhh yeah maybe u r right. the tabbar node can be an object or and function maybe this will help you ?

The acc function parameter is the navigationOptions of the current open screen for example an stackNavigator subpage.

export const CustomTabNavigator = TabNavigator({
...
Camera: {
        screen: CameraTab,
        navigationOptions: {
            tabBar: (state,acc) => {
                return {
                    visible: (acc && acc.visible !== 'undefined') ? acc.visible : true,
                    label: 'Camera',
                    icon: ({focused}) => createTabbarIcon('camera', focused)
                }
            },
        }
    },
...
});
zhaoyi0113 commented 7 years ago

Thanks for your response. I understand the general idea. But I don't know how to update the acc parameter in my component? Is the acc a state of my component or something else?

Psiiirus commented 7 years ago

i think the acc is this:

static navigationOptions = {
    tabBar: {
      label: '',
      icon: (obj) => {
        const image = obj.focused ? require('1.png') : require('2.png');
        return <Image style={{resizeMode:'contain', width:40, height: 40}}
                      source={image}
        />
      }
    },
  };
zhaoyi0113 commented 7 years ago

I see. When I update the acc value, the tab bar didn't get rendered. How can I make it re-render when the property value got changed?

vitorebatista commented 7 years ago

I have the same problem... Is there any example working?

dickfickling commented 7 years ago

My solution, to animate the tab bar hiding and showing based on navigatino params.

in your TabNavigator declaration:

const MyTabNav = TabNavigator(
  {
    Home: { Screen: HomeScreen },
    Profile: { Screen: ProfileScreen },
  },
  {
    tabBarComponent: TabBar,
  }
);

The Tab Bar itself:

import React, { Component } from 'react';
import { Animated } from 'react-native';
import { TabBarBottom } from 'react-navigation';

const TAB_BAR_OFFSET = -60;

export default class TabBar extends Component {
  constructor(props) {
    super(props);
    this.state = {
      offset: new Animated.Value(0),
    };
  }

  componentWillReceiveProps(props) {
    const oldState = this.props.navigation.state;
    const oldRoute = oldState.routes[oldState.index];
    const oldParams = oldRoute.params;
    const wasVisible = !oldParams || oldParams.visible;

    const newState = props.navigation.state;
    const newRoute = newState.routes[newState.index];
    const newParams = newRoute.params;
    const isVisible = !newParams || newParams.visible;

    if (wasVisible && !isVisible) {
      Animated.timing(this.state.offset, { toValue: TAB_BAR_OFFSET, duration: 200 }).start();
    } else if (isVisible && !wasVisible) {
      Animated.timing(this.state.offset, { toValue: 0, duration: 200 }).start();
    }
  }

  render() {
    return (
        <TabBarBottom {...this.props} style={[styles.container, { bottom: this.state.offset }]}/>
    );
  }
}

const styles = {
  container: {
    overflow: 'hidden',
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'white',
    elevation: 8,
  },
};

To use it, just call this.props.navigation.setParams({ visible: true/false }); in your HomeScreen or ProfileScreen. You can of course animate however you want - I choose to have it slide off the screen.

markjongkind commented 7 years ago

@richardfickling thanks for sharing! I had a problem with animating the component, because it's not an . Code below fixes that:

render() {
  return (
    <Animated.View style={[styles.container, { bottom: this.state.offset }]}>
      <TabBarBottom {...this.props} />
    </Animated.View>
  );
}
dickfickling commented 7 years ago

it's an animated view on master as of 3 days ago :) https://github.com/react-community/react-navigation/commit/d4ce9b08abf7aa725aca8305e841e1adf48d8a44

I had your code exactly until that change was merged.

SamMatthewsIsACommonName commented 7 years ago

Thanks @richardfickling @markjongkind this was a great guide. I was having trouble with setParams as I had a stack navigator nested inside a tab navigator, so what i ended up doing was making the TabBar component a connected (redux) component and going off of the props:

  componentWillReceiveProps(nextProps) {
    const wasVisible = this.props.tabBarOpen;
    const isVisible = nextProps.tabBarOpen;

    if (wasVisible && !isVisible) {
      Animated.timing(this.state.offset, { toValue: TAB_BAR_OFFSET, duration: 200 }).start();
    } else if (isVisible && !wasVisible) {
      Animated.timing(this.state.offset, { toValue: 0, duration: 150 }).start();
    }

then:

const mapStateToProps = (state) => ({
  tabBarOpen: state.admin.tabBarOpen,
});

export default connect(mapStateToProps)(TabBar);

Thanks for the guide!

dickfickling commented 7 years ago

@satya164 a recent update change to react-native-tab-view (https://github.com/react-native-community/react-native-tab-view/commit/c80fdff7bdb59b281c324bb6fb0dd1f213c1aae6) broke my workaround using an absolutely positioned tab view. Any chance we can add a way to send a style prop to the container view (or find a cleaner solution to the zIndex bug)? I'll send a PR for the style prop if that works for you

satya164 commented 7 years ago

Hmm. I might have an idea on how to fix that. Let's see.

hama commented 6 years ago

@satya164 what idea ?

chiragpurohit71085 commented 6 years ago

Hi All

I have just used simple react-navigation and I have stacked navigation

i.e

StackNavigator TabNavigator -- (stack navigor - view1,view2 etc..)

Now how I can show/hide tab bar in view1, after it is rendered?

Note: I have not used react-native-tab-view or no other packages.

Please guide me in this.

chiragpurohit71085 commented 6 years ago

Please also let me know if there is any alternative for this....

chiragpurohit71085 commented 6 years ago

Hi @richardfickling

your solution worked. Thanks you saved my life...

chiragpurohit71085 commented 6 years ago

oops this.props.navigation.setParams({ visible: true/false }); is not working when I call this from inner component for example...

When we have something like

Stacknavigator --Tabnavigator ----Stacknavigator ------Screen1(somehow works here)-->navigatates to screen-2 (not working here)

Whether I am missing anything here?

callmejm commented 6 years ago

@richardfickling and @markjongkind thanks for your both suggestion and solution. I got one problem is my indicatorStyle not apply to the TabBar component, any idea ?

joecaraccio commented 6 years ago

Any updates to this?

gamingumar commented 6 years ago

I found a Working Solution around this: in the screen you want to hide tab bar update the navigation option. the key is enabling animationEnabled to true and hide the tabBar using tabBarVisible property.

static navigationOptions = ({navigation}) => ({
  tabBarVisible: (navigation.state.params && navigation.state.params.hideTabBar) === true,
  animationEnabled: true
)}

make tabBar visible in componentWillMount.

componentWillMount() {
    const setParamsAction = NavigationActions.setParams({
      params: {hideTabBar: true}
    });
    this.props.navigation.dispatch(setParamsAction);
}

and in componentWillUnmount hide the tabBar again

componentWillUnmount() {
    const setParamsAction = NavigationActions.setParams({
      params: {hideTabBar: false}
    });
    this.props.navigation.dispatch(setParamsAction);
}
brentvatne commented 6 years ago

please re-post to https://react-navigation.canny.io/feature-requests if this is important for you. re-organizing issues to focus on bug reports here and feature requests on canny. thanks!

Donhv commented 6 years ago

same issue when I try show fullscreen modal? Who can help me?

schumannd commented 6 years ago

@gamingumar what is animationEnabled for? For me it also worked by simply setting

class ScreenWhereTabbarIsHidden extends React.Component {
  static navigationOptions = {
    tabBarVisible: false,
  }
}
gamingumar commented 6 years ago

@schumannd the problem happens when you have a back button. which doesn't work if animationEnabled is not true

schumannd commented 6 years ago

I am using a custom back button like so:

StackNavigator({
  ...
  }, {
    navigationOptions: ({ navigation }) => ({
      headerLeft: <BackButton navigation={navigation} />,
  }),
});

and did not encounter any problems. Can you elaborate why the option would break the back button?

vksgautam1 commented 6 years ago

unable to find working solution out of above.

adamawang commented 6 years ago

I was able to get a working solution using route keys. My setup is a TabNavigator with a nested StackNavigator so YMMV.

In TabNavigator, use whatever param you want:

screen: NavigationCamera,
navigationOptions: ({ navigation }) => {
  const showTabBar = navigation.state && navigation.state.routes && navigation.state.routes[0] && navigation.state.routes[0].params ? navigation.state.routes[0].params.showTabBar : true;
  return {
    tabBarLabel: 'Live',
    path: '/camera',
    tabBarIcon: ({ tintColor }) => (
      <Ionicons
        name={'ios-radio-outline'}
        size={28}
        style={{ color: tintColor, top: 2 }}
      />
    ),
    tabBarVisible: showTabBar,
  };
},

In your component where you want to hide the tab bar, do your boolean check and set showTabBar with the route key:

const setParamsAction = NavigationActions.setParams({
  params: { showTabBar: false/true },
  key: this.props.navigation.state.key,
});
this.props.navigation.dispatch(setParamsAction);
vksgautam1 commented 6 years ago

@adamawang i tried your solution but it did not worked

forster-thomas commented 6 years ago

@adamawang u'r awesome!

@vksgautam1 to work him answer, change this: const setParamsAction = NavigationActions.setParams({ params: { showTabBar: false/true }, key: this.props.navigation.state.key, }); this.props.navigation.dispatch(setParamsAction);

to this: const setParamsAction = this.props.navigation.setParams({ params: { showTabBar: true }, key: this.props.navigation.state.key, }); this.props.navigation.dispatch(setParamsAction);

Aidurber commented 6 years ago

For anyone still figuring out how to hide navigation elements dynamically.

If you're using redux, and custom headers/tab bars/whatever.

Create a UI ducks module like:

export interface State {
  headerHidden: boolean;
  topTabBarHidden: boolean;
  bottomTabBarHidden: boolean;
}

Then in your custom header just return hull if headerHidden is true:

const NavBar: React.SFC<NavBarProps> = props =>
  props.headerHidden ? null : <Header {...props} />;

This is what we're using when we want fullscreen video in nested navigators. Hopefully it helps someone.

SmiljanicN commented 6 years ago
// Dynamically hidding - simple
// In stack navigation add default
screen: yourScreen,
navigationOptions: {
    tabBarVisible: false
}

// On component add
static navigationOptions = ({ navigation }) => ({
    title: 'Wall',
    tabBarVisible: navigation.getParam('tabBarVisible') === false ? false : true
});

// trigger
this.props.navigation.setParams({ tabBarVisible: false/true });
gaithoben commented 6 years ago

@SmiljanicN , This does not work for me in the componentDidUpdate life cycle method. Where could i be wrong?

rnnyrk commented 6 years ago

@SmiljanicN this doesn't work inside a StackNavigator?

anhtukhtn commented 5 years ago

With me, this one works well react-navigation: "2.2.0"

// In Component
navigationOptions = (navigation) => {
  const visible = (navigation.state && navigation.state.routes[0].params) ?
    navigation.state.routes[0].params.showTabBar
    : true; // default is show

  return {
    tabBarVisible: visible,
  };
};

Then invoke

this.props.navigation.setParams({ showTabBar: show });

isaacnass commented 5 years ago
  tabBarVisible: !navigation.state || (navigation.state.routes[navigation.state.routes.length - 1] || {}).routeName !== 'whateverroutenameyouwanttohideon'
vksgautam1 commented 5 years ago

@forster-thomas const setParamsAction = this.props.navigation.setParams({ params: { showTabBar: false }, key: this.props.navigation.state.key, });

in console.log it shows boolean true and props shows params undefined. edit - so i changed it to const setParamsAction = this.props.navigation.actions.setParams({ params: { showTabBar: false }, key: this.props.navigation.state.key, }); now i am getting {"type":"Navigation/SET_PARAMS","key":"id-1530608852402-1","params":{"params":{"showTabBar":false},"key":"id-1530608852402-1"}}

and dispatching it but it is not hiding;

const showTabBar = navigation.state && navigation.state.routes && navigation.state.routes[0] && navigation.state.routes[0].params ? navigation.state.routes[0].params.showTabBar : true; in console navigation is not showing routes key

toanphan0303 commented 5 years ago

@vksgautam1 You should be careful with "navigation.state.routes[0]". The params may not at position 0 of array navigation.state.routes. You can print out value of navigation.state.routes to get exact position. In my case: navigation.state.routes[1].params.params.showTabBar

vksgautam1 commented 5 years ago

@toanphan0303 Thanks . i am printing the data but it is not showing routes key

michelewang commented 5 years ago

This is a solution that worked for me! Applicable if the Tab Navigator contains Stack Navigators and the screens you want to hide are in the Stack Navigators

in the file of the stack, insert this after you have exported/created the class

export default YourStack
const hiddenTabBars = ['Route1', 'Route2'] //these are the RouteNames of the screens you want to hide the tab bars on
YourStack.navigationOptions = ({ navigation }) => {
  let tabBarVisible = true
  if (
    hiddenTabBars.includes(
      navigation.state.routes[navigation.state.index].routeName,
    )
  ) {
    tabBarVisible = false
  }
  return {
    tabBarVisible,
  }
chaoliu commented 5 years ago

@markjongkind it works . But the tarbar will blink out,especially when you route back with the gesture . Any good ideas about this.

tufailra97 commented 5 years ago

In my situation the bottom tab bar is hidden by default. This is the code for the component:

import React, { Component } from 'react'
import {View, Text } from 'react-native';
import { createStackNavigator, NavigationActions } from 'react-navigation';
import Feed from '../components/Feed';
import Camera from '../components/Camera'

const Router = createStackNavigator({
  Feed : {
    screen : Feed
  },
  Camera : {
    screen : Camera
  }
});

export default class Home extends Component {
  static navigationOptions = ({navigation}) => ({
    tabBarVisible: (navigation.state.params && navigation.state.params.hideTabBar) === true,
    animationEnabled: true
  })

  componentWillMount() {
    const setParamsAction = NavigationActions.setParams({
      params: {hideTabBar: true}
    });
    this.props.navigation.dispatch(setParamsAction);
  }

  componentWillUnmount() {
    const setParamsAction = NavigationActions.setParams({
      params: {hideTabBar: false}
    });
    this.props.navigation.dispatch(setParamsAction);
  }

  render() {
    return (
      <View style = {{flex : 1}}>
        <Router/>
      </View>
    )
  }
}

Link Snack

johanntony commented 5 years ago

一顿操作猛如虎啊,没有一个能用的啊

Changdao commented 4 years ago

The solution worked well. You must check your router hierarchy to keep the param corresponding to the right one. My method is to console.log the navigation in the navigationOptions function to check where is the params object, maybe they are very deep.

munafmohammed commented 4 years ago
Screenshot 2020-02-04 at 1 06 37 PM

@richardfickling Getting this crash on using the code suggested by you.

IgorThierry commented 4 years ago

@SmiljanicN thanks man!

xilin commented 4 years ago

If you have a navigation struction with nested stack in tab:

You can pass tab's navigation through screenProps as a rootNavigation field, and use it later in the Home component to update route params.

numandev1 commented 4 years ago

here is a solution https://stackoverflow.com/a/62134625/8079868

BAE86AEC1946B6AB60CCD7452DC811F0 commented 3 years ago

@richardfickling感谢您的分享!我在为组件设置动画时遇到问题,因为它不是。以下代码修复了以下问题:

render() {
  return (
    <Animated.View style={[styles.container, { bottom: this.state.offset }]}>
      <TabBarBottom {...this.props} />
    </Animated.View>
  );
}

in v5.0 <Animated.View> Where to put it?