11

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?

David Schumann
  • 13,380
  • 9
  • 75
  • 96
DerpyNerd
  • 4,743
  • 7
  • 41
  • 92
  • Its just an idea. Don't know if its gonna work or not but, Did you try to create 2 different TabNavigators and conditional render the correct TabNavigator accourding to "account" prop in DynamicTabBar? – bennygenel Sep 22 '17 at 17:29
  • @bennygenel I'm afraid that would lead to the same conclusion. The navigation tree is built completely before the user is logged in. The condition would always render the TabNavigator that corresponds to false. – DerpyNerd Sep 22 '17 at 17:39
  • But if you think about it, the Main screen of the StackNavigator is going to be rendered after you push it to the stack, this means the DynamicTabBar component should render after you login successfully. The question is, can I navigate to Main screen without login? But then again when I move back to Login screen, Main Screen should unmount and mount again when I login and navigate to Main screen. I might be missing some structure of your logic but it seems doable to me. – bennygenel Sep 22 '17 at 17:50
  • That was my initial thought as well, but the tabnavigator itself has to be defined in a static context, otherwise; linking the routers `DynamicTabBar.router = Tabs.router;` is not possible. This means I cannot redefine the tabnavigator once I import `DynamicTabs` – DerpyNerd Sep 23 '17 at 07:03
  • 1
    I tried generating the tabs in a static function within `DynamicTabs` but when the app generates the navigation tree, it complains about undefined when evaluating `navigation.state.routes.forEach` – DerpyNerd Sep 23 '17 at 07:15

3 Answers3

6

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,
  }
)
Tim Morgan
  • 1,164
  • 9
  • 13
5

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.

David Schumann
  • 13,380
  • 9
  • 75
  • 96
DerpyNerd
  • 4,743
  • 7
  • 41
  • 92
  • If you do it like this, passing in the tintColor, then the icon will change colors when it's the active tab: `tabBarIcon: ({ tintColor }) => (` – alfonso Dec 07 '17 at 22:25
  • How do yoy manage to update the render function by just using `Account.User.CanEnter` ? It is not a state or props variable of the component right? – David Schumann May 18 '18 at 14:23
  • @DavidNathan, No. You see `Main: {screen: DynamicTabBar, ...` right? That means every time the main navigator is rebuild/refreshed, the render function will run and `CanEnter` may have been changed. `Accounts` is simply imported inside the `DynamicTabBar` file. Sorry I can't give you the specifics because I don't have access to the code anymore – DerpyNerd May 18 '18 at 15:57
0

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',
  },
});
Darkhan ZD
  • 580
  • 8
  • 14