Skip to content
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

Dynamic tab setup in TabNavigator #3945

Closed
MSSPL-PiyaleeMaiti opened this issue Apr 10, 2018 · 26 comments
Closed

Dynamic tab setup in TabNavigator #3945

MSSPL-PiyaleeMaiti opened this issue Apr 10, 2018 · 26 comments

Comments

@MSSPL-PiyaleeMaiti
Copy link

@MSSPL-PiyaleeMaiti MSSPL-PiyaleeMaiti commented Apr 10, 2018

Dynamic tab screen setup using TabNavigator

Current Behavior

  • Tab screen setup static
  • Can't change Tab screen setup according to login and not login

Expected Behavior

  • can change RouteConfigs according to login status in app
  • also can change TabNavigatorConfig accroding to login status in app

How to reproduce

  • I need one tab in home page, but after login I need three tab in home page
  • Also I want that tab setup inside a stack navigator, and also getting that stack route in between tab navigator.
  • I want to dynamic this tab navigator setup according to login status in app
  • Example of code is below:
import React, { Component } from 'react';
import { Text, View, StyleSheet, Button, Dimensions } from 'react-native';
import { StackNavigator, TabNavigator} from 'react-navigation';
let GlobalVariable = { loggedIn : false};
let routeTab, routeConfig;
if(GlobalVariable.loggedIn) {
  routeTab = {
    Feed: {
      screen: Feed
    },
    Notification: {
      screen: Notification
    },
    Profile: {
      screen: Profile
    }
  };

  routeConfig = {
    navigationOptions:{
      swipeEnabled: false,
    },
    tabBarPosition:'bottom',
    tabBarOptions: {
      showLabel: false,
      showIcon: true,
    }
  };

} else {
  routeTab = {
    Feed: {
      screen: Feed
    }
  };

  routeConfig = {
    navigationOptions:{
      swipeEnabled: false,
      tabBarVisible: false,
    }
  };
}

export const TabNavigatorThree = TabNavigator(routeTab, routeConfig);

class Home extends Component {
	static router = TabNavigatorThree.router;
	render() {
    console.log('GlobalVariable', GlobalVariable.loggedIn);
    return (
      <TabNavigatorThree navigation={this.props.navigation} />
    );
	}
}

class Feed extends Component {
   constructor(props) {
    super(props);
  }

  loginClick = () => {
      GlobalVariable.loggedIn = true;
      this.props.navigation.navigate('Login');
  }
  
  loginOut = () => {
      GlobalVariable.loggedIn = false;
      this.props.navigation.navigate('Home');
  }

  render() {
    return (
        <View style={{flex:1, paddingTop:20, }}>
          <Button title="Log In" onPress={this.loginClick}/>
          <Button title="Log Out" onPress={this.loginOut}/>
          <View style={{flex:1, backgroundColor:'yellow'}}>
            <Text>Feed Main Page</Text>
            <Button title="Details Page" onPress={() => this.props.navigation.navigate('Details')}/>
            <Button title="Chat Page" onPress={() => this.props.navigation.navigate('Chat')}/>
          </View>
        </View>
    );
  }
}

class Notification extends Component {
  render() {
    return (
      <View style={{flex:1, backgroundColor:'orange'}}>
        <Text>Notification Main Page</Text>
      </View>
    );
  }
}

class Profile extends Component {
  render() {
    return (
      <View style={{flex:1, backgroundColor:'blue'}}>
        <Text>Profile Main Page</Text>
      </View>
    );
  }
}

class Details extends Component {
  render() {
    return (
      <View style={{flex:1, backgroundColor:'purple'}}>
        <Text>Details Main Page</Text>
      </View>
    );
  }
}

class Chat extends Component {
  render() {
    return (
      <View style={{flex:1, backgroundColor:'orange'}}>
        <Text>Chat List Main Page</Text>
      </View>
    );
  }
}

const App = StackNavigator({
  Home : {
    screen : Home,
  },
  Details : {
    screen : Details
  },
  Chat : {
    screen : Chat
  },
  Login: {
    screen: Login,
  },
});

export default App;

Now here from feed, notification and profile page, I want to access the details, chat and login page. And also get feed, notification and profile page tab dynamic before and after login.

Your Environment

software version
react-navigation ^1.5.11
react-native 0.55.1
node v6.14.1
npm or yarn 3.10.10
@brentvatne

This comment has been minimized.

Copy link
Member

@brentvatne brentvatne commented Apr 27, 2018

@brentvatne brentvatne closed this Apr 27, 2018
@Msspl-PrashenjeetRoy

This comment has been minimized.

Copy link

@Msspl-PrashenjeetRoy Msspl-PrashenjeetRoy commented Apr 30, 2018

@brentvatne I have seen that you are not giving a solution to the issue and just closed it. And suggesting it to some help.html page and saying that ask it in StackOverflow. Why ??
You are the owner of the repo and the plugin. So I think it's better always to ask you about the required feature. correct?

@crazycodeboy

This comment has been minimized.

Copy link

@crazycodeboy crazycodeboy commented Jul 13, 2018

To implement the dynamic Tab functionality, we can implement a DynamicTabNavigator:
react-navigation-dynamic-tab

import {BottomTabBar, createBottomTabNavigator} from 'react-navigation-tabs';

const TABS = {//在这里配置页面的路由
    Page1: {
        screen: Page1,
        navigationOptions: {
            tabBarLabel: 'Page10',
            tabBarIcon: ({tintColor, focused}) => (
                <Ionicons
                    name={focused ? 'ios-home' : 'ios-home-outline'}
                    size={26}
                    style={{color: tintColor}}
                />
            ),
        }
    },
    Page2: {
        screen: Page2,
        navigationOptions: {
            tabBarLabel: 'Page2',
            tabBarIcon: ({tintColor, focused}) => (
                <Ionicons
                    name={focused ? 'ios-people' : 'ios-people-outline'}
                    size={26}
                    style={{color: tintColor}}
                />
            ),
        }
    },
    Page3: {
        screen: Page3,
        navigationOptions: {
            tabBarLabel: 'Page3',
            tabBarIcon: ({tintColor, focused}) => (
                <Ionicons
                    name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
                    size={26}
                    style={{color: tintColor}}
                />
            ),
        }
    },
};

export default class DynamicTabNavigator extends React.Component {
    constructor(props) {
        super(props);
        console.disableYellowBox = true;
    }

    /**
     * 获取动态的Tab
     * @returns {*}
     * @private
     */
    _tabNavigator() {
        let tabs = {};
        if (this.props.navigation.state.params.tabs) {
            /**
             * 取出上一个页面传过来的要显示的tab参数,也可以是从服务端下发的的Tab的配置,
             * 比如显示createBottomTabNavigator中的那些Tab,
             * 这个配置页可以是在其他页面获取之后通过AsyncStorage写入到本地缓存,
             * 然后在这里读取缓存,也可以通过其他方式如props、global config等获取
             ***/
            this.props.navigation.state.params.tabs.forEach(e => {//根据需要定制要显示的tab
                tabs[e] = TABS[e];
            })

        } else {
            const {Page1, Page2} = TABS;//根据需要定制要显示的tab
            tabs = {Page1, Page2};
            Page1.navigationOptions.tabBarLabel = 'P1';//动态修改Tab的属性
        }
        return createBottomTabNavigator(tabs, {//应用修改后的tab
            tabBarComponent: TabBarComponent,
            tabBarOptions: {
                activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
            }
        });
    }

    render() {
        const Tabs = this._tabNavigator();
        return (
            <Tabs/>
        );
    }
}

Then, use this DynamicTabNavigator in createStackNavigator:

export const AppStackNavigator = createStackNavigator({
    HomePage: {
        screen: HomePage
    },
   ...
    TabNav: {
        screen: DynamicTabNavigator,
        navigationOptions: {//在这里定义每个页面的导航属性,静态配置
            title: "This is TabNavigator.",
        }
    }
}, {
    navigationOptions: {
        // header: null,// 可以通过将header设为null 来禁用StackNavigator的Navigation Bar
    }
});
@ds8k

This comment has been minimized.

Copy link

@ds8k ds8k commented Aug 7, 2018

@crazycodeboy I've implemented a similar solution but it results in a Yellowbox telling me I shouldn't render another navigator and links off to "common mistakes". It looks like you're disabling yellowboxes on this view, but I'd like to avoid that.

@edwinlimlx

This comment has been minimized.

Copy link

@edwinlimlx edwinlimlx commented Oct 20, 2018

Thank @crazycodeboy ! however like what @ds8k mentioned, the yellowboxes are showing... and not sure if disabling them are ideal.

any better workflow for implmenting dynamic createBottomTabNavigator?

Create to have a different bottomTabNavigator for an admin and default user role

@jose-verissimo

This comment has been minimized.

Copy link

@jose-verissimo jose-verissimo commented Dec 5, 2018

@crazycodeboy Excellent solution. Working now! But yeah like @ds8k and @edwinlimlx said there is a warning.

Any ideas?

@linoleum00

This comment has been minimized.

Copy link

@linoleum00 linoleum00 commented Dec 12, 2018

Hey guys, I am trying to implement @crazycodeboy 's solution, but I don't know what I'm missing because my DynamicTabComponent is not being read by my createStackNavigator.

I'm currently using versin 3.0.5 of react-navigation

DynamicTabComponent.js:

import {createBottomTabNavigator} from 'react-navigation'
import React, {Component} from 'react';

export default class DynamicTabComponent extends Component {

    constructor(props) {
        super(props);
    }

    _tabNavigator() {
        const tabs = {
            Home: {
                screen: HomeContainer
            },
            Profile : {
                screen: ProfileContainer
            },
        }
        return createBottomTabNavigator(
            tabs
        )
    }

    render() {
        const Tabs = this._tabNavigator();
        return (
            <Tabs/>
        );
    }
}

Navigation.js:

const RootStack = createStackNavigator(
    {
        SplashContainer: {
            screen: SplashContainer
        },
        TabNav: {
            screen: DynamicTabComponent
        },

    }, {
        initialRouteName: 'SplashContainer',
        mode: 'modal',
        headerMode: 'none',
        navigationOptions: {
            headerVisible: false,
        }
    });

export const AppContainer = createAppContainer(RootStack);

Thanks!

@linoleum00

This comment has been minimized.

Copy link

@linoleum00 linoleum00 commented Dec 12, 2018

I finally did it, apparently you need to specify a static TabRouter prop within the component:

import {createStackNavigator, TabRouter} from 'react-navigation'
...

static router = TabRouter({
       Home: {
                screen: HomeContainer
            },
            Profile : {
                screen: ProfileContainer
            },
    })
...
@linoleum00

This comment has been minimized.

Copy link

@linoleum00 linoleum00 commented Dec 14, 2018

The problem now is that having to add the static router var, I can't access this.props, and I can't init the dynamic tab navigator in a dynamic way.

If for example, my static route has routes Home and Profile, and in the _tabNavigator() function I change the routes to say Splash and Profile, I'm having an error where react-navigation expects a Home route and the app crashes.

@crazycodeboy I don't know how you get it to work without the static route var.

@crazycodeboy @ds8k @jose-verissimo any thoughts about this??

Thanks guys

PD: I'm using v3

@MSSPL-PiyaleeMaiti

This comment has been minimized.

Copy link
Author

@MSSPL-PiyaleeMaiti MSSPL-PiyaleeMaiti commented Dec 15, 2018

Guys, I have the problem with the route config dynamic. Once I set route config after that I can't change that config depends on login in the app. Is there any solution? Please let me know.
@linoleum00 dynamic tab can change depends on page name?

@devpascoe

This comment has been minimized.

Copy link

@devpascoe devpascoe commented Dec 21, 2018

Anyone got this working with React Navigation 3?

@dlombardi

This comment has been minimized.

Copy link

@dlombardi dlombardi commented Dec 24, 2018

What's the reason that dynamic tabs are not supported?

@linoleum00

This comment has been minimized.

Copy link

@linoleum00 linoleum00 commented Dec 26, 2018

In my app, before the user login I have to choose the country from which I want to manage the data. The selected country is stored in my redux state, and depending on that selection I show my tabs.
And having to instantiate TabRouter as static I can't read my redux state and change the routes.

@devpascoe

This comment has been minimized.

Copy link

@devpascoe devpascoe commented Jan 9, 2019

I ended up writing something like this to only load the component once the tab configuration was fetched from an api and saved into a global variable and used state to only show the component once ready.

  fetchTabs = async () => {
    const fetchUrl = `urltoapi/menu`
    const response = await fetch(fetchUrl)
    global.tabs = await response.json()
    AppScreen = require('./screens/AppScreen').default
    this.setState({ loadedTabs: true })
  }

...and in AppScreen its default returns the createBottomTabNavigator with the tabs object being the result of the following tabs() function

const tabs = () => {
  const tabItems = {}
  global.tabs.forEach(tab => {
    if (tab.screen == 'Feed') {
      tabItems['Feed'] = FeedStack
    } else if (tab.screen == 'Chat') {
      tabItems['Chat'] = ChatStack
    } else if (tab.screen == 'Profile') {
      tabItems['Profile'] = MyProfileStack
    } else if (tab.screen == 'Contact') {
      tabItems['Contact'] = ContactScreen
    }
  })
  return tabItems
}

So i'm basically doing whatever i can to delay loading and then showing the createBottomTabNavigator until i have prepared the tabs object for it to use since it needs that result returned synchronously.

@booboothefool

This comment has been minimized.

Copy link

@booboothefool booboothefool commented Jan 14, 2019

@crazycodeboy's solution worked for me in React Navigation 2, but broke upon upgrading to React Navigation 3. Anyone have a new solution?

@ghost

This comment has been minimized.

Copy link

@ghost ghost commented Jan 31, 2019

The only solution we got to work is using a completely different AppNavigator based on the redux state. Totally unnecessary if there was an easier solution to specify the routes/tabs based on props.

@ekim-novus

This comment has been minimized.

Copy link

@ekim-novus ekim-novus commented Feb 22, 2019

@devpascoe how did you end up using AppScreen when you set up your app's main navigator? (That is, I'm wondering if you passed the AppScreen into another navigator like SwitchNavigator)

@GreenHand-zmy

This comment has been minimized.

Copy link

@GreenHand-zmy GreenHand-zmy commented Apr 6, 2019

@booboothefool Have you found a solution?

@wildseansy

This comment has been minimized.

Copy link

@wildseansy wildseansy commented Apr 15, 2019

I also got it all working using AppNavigator. Thx for the suggestion @felixus95. It was working for me in React Navigation v2, but broke in v3.

@Dallas62

This comment has been minimized.

Copy link

@Dallas62 Dallas62 commented May 15, 2019

Hi!
@wildseansy @felixus95 @booboothefool

I didn't try with your example, but there is some extra changes:

// Maybe in _tabNavigator() ?
DynamicTabComponent.router = Tabs.router;
DynamicTabComponent.navigationOptions = Tabs.navigationOptions;

Some informations in:
https://reactnavigation.org/docs/en/common-mistakes.html

Alternatively, the following would also work because it exposes the router static on AuthenticationScreen and threads through the navigation prop:

For navigationOptions, it's just luck.

In my use case, I don't need to change tab but remove all tabs, so it's not the same implementation.

@johnlim5847

This comment has been minimized.

Copy link

@johnlim5847 johnlim5847 commented Sep 12, 2019

@felixus95's answer just have to wrap in createAppContainer in order for it to work on V3

import { createAppContainer } from 'react-navigation';
 render = () => {
    const Tabs = createAppContainer(this.configureTabs());
    return <Tabs />;
  };
@solidfox

This comment has been minimized.

Copy link

@solidfox solidfox commented Sep 15, 2019

I get concerned reading the solution by @crazycodeboy. If I understand it correctly it is creating a new component class on each render, then rendering that. This effectively deactivates React for anything nested within the tabs since React will always consider the new class to be completely different from the old one. Expect weird, unexpected behavior.

@satya164

This comment has been minimized.

Copy link
Member

@satya164 satya164 commented Sep 15, 2019

Yes, please don't create navigators inside render. The above solution is not only going to be disconnected from rest of the navigation tree, but will also remount the component on every render, which will lose any local state and is also very expensive.

Dynamic configuration of navigators will be possible in React Navigation 5: https://reactnavigation.org/next

@wildseansy

This comment has been minimized.

Copy link

@wildseansy wildseansy commented Sep 15, 2019

The way I have solved this problem is storing a reference in the owner component. Roughly the following:

import { createAppContainer } from 'react-navigation';
class MyComponent extends React.Component {
 constructor() {
  super()
  this.tabs = createAppContainer(this.configureTabs())
 }
 render = () => {
    return this.tabs;
  };
}
@johnlim5847

This comment has been minimized.

Copy link

@johnlim5847 johnlim5847 commented Sep 17, 2019

For anyone who just wants to hide tabs dynamically, you can consider using this react-navigation-selective-tab-bar package.

The implementation is fairly simple.

NOTE: I'm using remote config to control what to hide and show

import React, { Component } from 'react';
import BottomTabBar from 'react-navigation-selective-tab-bar';

import { RemoteConfig } from '../helpers';

export default class DynamicTabNavigator extends Component {
  state = {
    whiteListNavigator: [],
  };
  componentDidMount = () => {
    this.mapRemoteConfigToState();
  };
  mapRemoteConfigToState = async () => {
    const displayToggleValue = await RemoteConfig.getDisplayToggleValueByKey({
      key: 'navigator_control_display',
    });
    const whiteListNavigator = [];
    if (displayToggleValue.home_tab) {
      whiteListNavigator.push('HomeTab');
    }
    if (displayToggleValue.reward_tab) {
      whiteListNavigator.push('RewardsTab');
    }
    if (displayToggleValue.setting_tab) {
      whiteListNavigator.push('MoreTab');
    }
    this.setState({
      whiteListNavigator,
    });
  };
  render = () => {
    return (
      <BottomTabBar {...this.props} display={this.state.whiteListNavigator} />
    );
  };
}

and then you import DynamicTabNavigator to your router like this

const MainNavigator = createBottomTabNavigator(
  {
    HomeTab: {
      screen: LandingPage,
      navigationOptions: {
        tabBarIcon: ({ tintColor }) => (
          <Image
            source={require('app/assets/img/home.png')}
            style={{ tintColor }}
          />
        ),
      },
    },
    RewardsTab: {
      screen: RedemptionNavigator,
      navigationOptions: {
        tabBarIcon: ({ tintColor }) => (
          <Image
            source={require('app/assets/img/rewards.png')}
            style={{ tintColor }}
          />
        ),
      },
    },
    MoreTab: {
      screen: SettingsNavigator,
      navigationOptions: {
        tabBarIcon: ({ tintColor }) => (
          <Image
            source={require('app/assets/img/more.png')}
            style={{ tintColor }}
          />
        ),
      },
    },
  },
  {
    initialRouteName: 'HomeTab',
    transitionConfig,
    tabBarPosition: 'bottom',
    tabBarComponent: props => {
      return <DynamicTabNavigator {...props} />;
    },
    lazy: true,
    swipeEnabled: false,
    animationEnabled: false,
  },
);
@mgarabedian

This comment has been minimized.

Copy link

@mgarabedian mgarabedian commented Oct 9, 2019

I was able to get Tabs to hide themselves by using a custom tabBarButtonComponent in my static navigationOptions, which then has access to navigation params. The tab component is still active of course, but it proved useful for my needs.

In your Tab component:

   static navigationOptions = ({ navigation }) => {
        const { params } = navigation.state;
        const showTab = params ? params.showTab : false;
        return {
           tabBarButtonComponent: (props) => {
                const isFocused = props.accessibilityStates.length > 0 && props.accessibilityStates[0] === 'selected'
                return <TouchableWithoutFeedback  {...props}>
                    <View>
                        {isFocused && showTab && <SomethingFocused />}
                        {!isFocused && showTab && <SomethingNotFocused />}
                    </View>
                </TouchableWithoutFeedback>
            },
       }
}

Somewhere else in your component:

 this.props.navigation.setParams({
     showTab: true,
})

If your component does not have access to props.navigation, make sure it is exported with 'withNavigationFocus'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.