DerpyNerd
DerpyNerd

Reputation: 4803

Creating a TabNavigator with dynamic tabs

I'm relatively sure I found out it isn't possible, but I want to make sure there isn't a way.

The app in question starts off with an AppNavigator StackNavigator.

export const AppNavigator = StackNavigator({
    Login: {
        screen: Login,
        navigationOptions: ({navigation}) => ({
            title: 'Aanmelden',
            params: {
                nuke: navigation.state.params && !!navigation.state.params.nuke,
            },
        }),
    },
    Main: {
        screen: DynamicTabBar,
        navigationOptions: ({navigation}) => ({
            title: 'Follow-up',
        }),
    },
}, {
    mode: 'modal',
    headerMode: 'none',
});

export class AppWithNavigationState extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return <AppNavigator navigation={addNavigationHelpers({ dispatch: this.props.dispatch, state: this.props.navigationReducer })} />
    }
}

AppWithNavigationState.propTypes = {
    dispatch: React.PropTypes.func.isRequired,
    navigationReducer: React.PropTypes.object.isRequired,
};

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

export default connect(mapStateToProps)(AppWithNavigationState);

So far so good, it's just that the DynamicTabBar is should not be 'rendered' until the user has logged in (i.e. navigating from Login to Main).

Here's why

const Tabs = TabNavigator({
        Start: {
            screen: UserStackNavigator,
            navigationOptions: {
                tabBarIcon: (<Icon
                    type="font-awesome"
                    name="users"
                    color="#dddddd"
                    size={20}
                />),
            },
        },
        ...Account.User.CanEnter ? {
            ConditionalTab: {
                screen: ConditionalScreen,
                navigationOptions: {
                    tabBarIcon: (<Icon
                        type="font-awesome"
                        name="recycle"
                        color="#dddddd"
                        size={20}
                    />),
                },
            }} : {},
        Settings: {
            screen: Settings,
            navigationOptions: {
                tabBarIcon: (<Icon
                    type="font-awesome"
                    name="cog"
                    color="#dddddd"
                />),
            }
        }
    },{
        ...TabNavigator.Presets.AndroidTopTabs,
        tabBarPosition: "bottom",
        tabBarOptions: {
            activeTintColor: '#eaeb65',
            showIcon: true,
            showLabel: false,
            style: { backgroundColor: '#333' },
        }
    });

export default class DynamicTabBar extends React.Component {
    constructor(props) {
        super(props);
    }
    render() {
        return <Tabs navigation={this.props.navigation} />;
    }
}
DynamicTabBar.router = Tabs.router;

...Account.User.CanEnter ? { is always false because the TabNavigator is rendered before the user has logged in and Account is filled. A failed attempt, it seams.

I wanted to populate Tabs inside componentWillMount, but then I can't set the static router: DynamicTabBar.router = Tabs.router;

Any ideas on how to fix this?

Upvotes: 11

Views: 15425

Answers (3)

Tim Morgan
Tim Morgan

Reputation: 1184

I know this is old, but AFAICT there is no blessed way to do this still, in 2019. Here is the solution I came up with, and I think it's fairly simple.

Basically, I specify a custom tabBarComponent with a custom getButtonComponent method that either renders the button or not based on the current user. If the user shouldn't see the nav item, then it renders an empty view, which has the effect of hiding the navigation item.

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

class TabBar extends React.Component {
  render() {
    return (
      <BottomTabBar
        {...this.props}
        getButtonComponent={this.getButtonComponent}
      />
    )
  }

  getButtonComponent({ route }) {
    if (route.key === 'Other') {
      return () => <View /> // a view that doesn't render its children
    } else {
      return null // use the default nav button component
    }
  }
}

const AppNav = createBottomTabNavigator(
  {
    Home: HomeStack,
    Other: OtherScreen,
  },
  {
    tabBarComponent: TabBar,
  }
)

Upvotes: 6

Darkhan ZD
Darkhan ZD

Reputation: 610

Here is my solution. I'm using expo. I needed dynamic amount and content of tabs, which I'm going to get from API.

MainTabNavigator.js

import React from 'react';
import { Platform } from 'react-native';
import { createStackNavigator, createBottomTabNavigator } from 'react-navigation';

import TabBarIcon from '../components/TabBarIcon';
import SettingsScreen from '../screens/SettingsScreen';
import {getBrowerScreen} from '../screens/BrowserScreen';


const SettingsStack = createStackNavigator({
  Settings: SettingsScreen,
});

SettingsStack.navigationOptions = {
  tabBarLabel: 'Settings',
  tabBarIcon: ({ focused }) => (
    <TabBarIcon
      focused={focused}
      name={Platform.OS === 'ios' ? 'ios-options' : 'md-options'}
    />
  ),
};

// Tab params that we will get from API
var tabParams = [
  {title: 'Tab 1', url: 'https://mycar.city', icon: 'md-pricetags'},
  {title: 'Tab 2', url: 'https://mycar.city', icon: 'ios-play-circle'},
  {title: 'Tab 3', url: 'https://mycar.city', icon: 'ios-phone-portrait'},
  {title: 'Tab 4', url: 'https://global-trend.info', icon: 'ios-phone-portrait'},
],
tabs = {};

for (var i=0; i<tabParams.length; i++) {
  const tab = tabParams[i];

  tabs['Tab' + (i + 1)] = createStackNavigator({
    Links: getBrowerScreen(tab.url),
  });

  tabs['Tab' + (i + 1)].navigationOptions = {
    tabBarLabel: tab.title,
    tabBarIcon: ({ focused }) => (
      <TabBarIcon
        focused={focused}
        name={tab.icon}
      />
    ),
  };
}

//Adding another kind of tab
tabs.SettingsStack = SettingsStack;

export default createBottomTabNavigator(tabs);

BrowserScreen.js

import React from 'react';
import { View, StyleSheet, WebView } from 'react-native';
import { ExpoLinksView } from '@expo/samples';

export function getBrowerScreen(url) {

  return class BrowserScreen extends React.Component {
    static navigationOptions = {
      header: null,
    };

    render() {
      return (
        <View style={styles.container}>
          <WebView
            source={{ uri: url }}
            style={{ width: '100%' }}
          />
        </View>
      );
    }
  };
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
});

Upvotes: 0

DerpyNerd
DerpyNerd

Reputation: 4803

Yessssss! I think I found a way. The solution probably isn't following the design pattern, but this is what I came up with:

export default class DynamicTabBar extends React.Component {
    static router = TabRouter({
        Start: {
            screen: UserStackNavigator,
            navigationOptions: {
                tabBarIcon: (<Icon
                    type="font-awesome"
                    name="users"
                    color="#dddddd"
                    size={20}
                />),
            },
        },
        ...Account.User.CanEnter ? {
            ConditionalTab: {
                screen: ConditionalScreen,
                navigationOptions: {
                    tabBarIcon: (<Icon
                        type="font-awesome"
                        name="recycle"
                        color="#dddddd"
                        size={20}
                    />),
                },
            }} : {},
        Settings: {
            screen: Settings,
            navigationOptions: {
                tabBarIcon: (<Icon
                    type="font-awesome"
                    name="cog"
                    color="#dddddd"
                />),
            }
        }
    },{
        ...TabNavigator.Presets.AndroidTopTabs,
        tabBarPosition: "bottom",
        tabBarOptions: {
            activeTintColor: '#eaeb65',
            showIcon: true,
            showLabel: false,
            style: { backgroundColor: '#333' },
        }
    });
    constructor(props) {
        super(props);
    }

    render() {
        const tabs = TabNavigator({
            Start: {
                screen: UserStackNavigator,
                navigationOptions: {
                    tabBarIcon: (<Icon
                        type="font-awesome"
                        name="users"
                        color="#dddddd"
                        size={20}
                    />),
                },
            },
            ...Account.User.CanEnter ? {
                ConditionalTab: {
                    screen: ConditionalScreen,
                    navigationOptions: {
                        tabBarIcon: (<Icon
                            type="font-awesome"
                            name="recycle"
                            color="#dddddd"
                            size={20}
                        />),
                    },
                }} : {},
            Settings: {
                screen: Settings,
                navigationOptions: {
                    tabBarIcon: (<Icon
                        type="font-awesome"
                        name="cog"
                        color="#dddddd"
                    />),
                }
            }
        },{
            ...TabNavigator.Presets.AndroidTopTabs,
            tabBarPosition: "bottom",
            tabBarOptions: {
                activeTintColor: '#eaeb65',
                showIcon: true,
                showLabel: false,
                style: { backgroundColor: '#333' },
            }
        });

        return <Tabs navigation={this.props.navigation} />;
    }
}

The router is assigned in a static way and is built dynamically at runtime.

Upvotes: 5

Related Questions