Passing props to a dynamic TabNavigator
Solution 1
You can also leave the TabNavigator as is, and create a custom TabBar component with custom TabBarItem components. You can connect that custom TabBar to your redux state, and hide/display the custom TabBarItems according your needs.
And then you simply add all possible routes to the TabNavigator as you would always do.
Routes
const TabRoutes = createBottomTabNavigator({
First: {screen: SomeScreen},
Second: {screen: SomeStack},
Third: {screen: AnotherStack}
},{
initialRouteName: 'First',
tabBarComponent: CustomTabBar
});
CustomTabBar
Some basic example on how you could hide the tab bar items, so obviously this needs to be adjusted according your own requirements
import CustomTabBarItem from '...' ;
class CustomTabBar extends React.Component {
render() {
// a tab bar component has a routes object in the navigation state
const {navigation, appState} = this.props;
const routes = navigation.state.routes;
return (
<View style={styles.container}>
// You map over all existing routes defined in TabNavigator
{routes.map((route, index) => {
// This could be improved, but it's just to show a possible solution
if (appState && route.routeName === 'x') {
return <View/>;
} else if (!appState && route.routeName === 'y') {
return <View/>;
}
return (<CustomTabBarIcon
key={route.key}
name={route.routeName}
onPress={this.navigationHandler}
focused={navigation.state.index === index}
appState={appState}
/>);
})}
</View>
);
}
navigationHandler = (name) => {
const {navigation} = this.props;
navigation.navigate(name);
}
}
const styles = StyleSheet.create({
container: {
width: '100%',
flexDirection: 'row'
}
})
const mapStateToProps = (state) => {
return {
appState: state.app.appState // boolean
};
};
export default connect(mapStateToProps)(CustomTabBar);
CustomTabBarItem
class CustomTabBarItem extends React.PureComponent {
render() {
const {name, focused} = this.props;
return (
<View style={styles.tabItem}>
// Some icon maybe
<Text style={/*different style for focused / unfocused tab*/}>{name}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
tabItem: {
flex: 1
}
})
Solution 2
Ok after looking at your code I see what's the problem... the problem is.. you are not nesting your navigation prop the right way. When you nest A normal tabnavigator in a drawernavigator it works fine, because you are not rendering anything , you are doing it the react navigation way.
But.. when you try to use a dynamic tabnavigator, you are not returning a tabnavigator to the drawernavigator, you are returning a COMPONENT
export default class DynamicTabNavigator extends React.Component {}
, and inside his render function you have a tabnavigator...
So.. you have 2 possible solutions...
The first one being use just a function and call it without having a custom component
export function tabNavigator() {
let tabs = {};
const a = 2;
if (a > 1) { // the actual line is obviously different, I am trying to simplify the example
tabs = { RequestStack, ManageStack, MessagesStack, ProfileStack };
} else {
tabs = { WorkStack, ManageStack, MessagesStack, ProfileStack };
}
// console.log('in _tabNavigator. this.props.navigation:');
//console.log(navigation);
return createBottomTabNavigator(tabs, {
headerMode: 'none',
});
}
and in your root navigator
import {tabNavigator} from './TabNavigator'
const Tabs = tabNavigator()
export default createDrawerNavigator({
Drawer: MainDrawerNavigator,
Main: Tabs
}, {
contentComponent: props => <Drawer {...props} />,
});
Dont' know if that is gonna work
Second solution
Manually pass the navigation prop to all the screens of the dynamic tabnavigator, it's really ugly, but it's a workaround when you put a navigator inside a component
_tabNavigator() {
let tabs = {};
const a = 2;
if (a > 1) { // the actual line is obviously different, I am trying to simplify the example
tabs = { RequestStack: {screen:<RequestStack navigation={this.props.navigation}/>, navigationOptions: () => ({
tabBarLabel:'Request',
tabBarIcon:<YourComponent/>})},
ManageStack:{screen:<ManageStack navigation={this.props.navigation}/>}, MessagesStack:{screen:<MessagesStack navigation={this.props.navigation}/>},
ProfileStack:{screen:<ProfileStack navigation={this.props.navigation}/>}};
} else {
tabs = { WorkStack, ManageStack, MessagesStack, ProfileStack }; //repeat
}
console.log('in _tabNavigator. this.props.navigation:');
console.log(this.props.navigation);
return createBottomTabNavigator(tabs, {
headerMode: 'none',
});
Pro tip: don't do dynamic navigation inside a component, the navigation prop will be lost if you don't pass it manually
EDIT N2:
const ProfileStack = ({props}) => createStackNavigator({
Profile: {
screen: <Profile navigation={props.navigation}/>,
navigationOptions: () => ({
title: 'Profile'
})
}
}, {
initialRouteName: 'Profile'
});
Yossi
Lazy, unorganized programmer. I have your attention! I am kidding... The truth is that I am: Codaholic. A graduate from the Technion, Israel Institute of Technology (President’s Excellence: average grade of 94). Good knowledge of Javascript, React (mainly native), Redux, Firebase. Big fan of Spinoza, Borges, Gogol, Thomas Mann, Lou Reed, Coen brothers.
Updated on June 26, 2022Comments
-
Yossi almost 2 years
(also asked in https://github.com/react-navigation/react-navigation/issues/4059#issuecomment-453100740)
I have replaced a static TabNavigator with a dynamic one, and things seem to work.
However, props that have been passed as expected are not passed any more the same way.
Any idea how to fix this? Either by having the props passed as in the static solution, or by passing the required props (this.props.navigation).This is my top navigator:
export default createDrawerNavigator({ Drawer: MainDrawerNavigator, Main: MainTabNavigator }, { contentComponent: props => <Drawer {...props} />, });
This the static Tab Navigator and one of the Stacks:
const ProfileStack = createStackNavigator({ Profile: { screen: Profile, navigationOptions: () => ({ title: 'Profile' }) } }, { initialRouteName: 'Profile' }); ProfileStack.navigationOptions = { tabBarLabel: 'Profile', tabBarIcon: ({ focused }) => ( <TabBarIcon focused={focused} name= 'md-person' /> )}; const MainTabNavigator = createBottomTabNavigator({ RequestStack, ProfileStack }, { headerMode: 'none', initialRouteName: ProfileStack });
And the
Profile
screen:import React from 'react'; import { View, TouchableOpacity } from 'react-native'; import { Container, Header, Content, Text } from 'native-base'; export default class Profile extends React.Component { static navigationOptions = { header: null }; constructor(props) { super(props); } render() { console.log('in Profile. this.props:'); console.log(this.props); return ( <Container style={styles.container}> <Header> <TouchableOpacity onPress={() => this.props.navigation.openDrawer()}> <Icon name="md-more" /> </TouchableOpacity> </Header> <Content> <Text>aaa</Text> </Content> </Container> ); } }
A click on the "md-more" icon opens the drawer (this.props.navigation.openDrawer).
Using a dynamic tab navigator - openDrawer is not passed any more to 'Profile'.
when I replace the above static Tab Navigator with the following Dynamic one, this.props.navigation.openDrawer is not passed and therefore not defined in 'Profile' (Profile doesn't change, the change is only in the bottom tab navigator).
Here is the dynamic Tab Navigator:
export default class DynamicTabNavigator extends React.Component { constructor(props) { super(props); } _tabNavigator() { let tabs = {}; const a = 2; if (a > 1) { // the actual line is obviously different, I am trying to simplify the example tabs = { RequestStack, ManageStack, MessagesStack, ProfileStack }; } else { tabs = { WorkStack, ManageStack, MessagesStack, ProfileStack }; } console.log('in _tabNavigator. this.props.navigation:'); console.log(this.props.navigation); return createBottomTabNavigator(tabs, { headerMode: 'none', }); } render() { const Tabs = this._tabNavigator.bind(this)(); return ( <Tabs/> ); } }
This is the output of console.log() from DynamicTabNavigator:
in _tabNavigator. this.props.navigation: Object { "actions": Object { "closeDrawer": [Function closeDrawer], "goBack": [Function goBack], "navigate": [Function navigate], "openDrawer": [Function openDrawer], "setParams": [Function setParams], "toggleDrawer": [Function toggleDrawer], }, "addListener": [Function addListener], "closeDrawer": [Function anonymous], "dangerouslyGetParent": [Function anonymous], "dispatch": [Function anonymous], "getChildNavigation": [Function getChildNavigation], "getParam": [Function anonymous], "getScreenProps": [Function anonymous], "goBack": [Function anonymous], "isFocused": [Function isFocused], "navigate": [Function anonymous], "openDrawer": [Function anonymous], "router": undefined, "setParams": [Function anonymous], "state": Object { "key": "Main", "params": undefined, "routeName": "Main", }, "toggleDrawer": [Function anonymous], }
This is the output of console.log() from Profile, when DynamicTabNavigator is in place:
(I expected all props, like for instance, openDrawer, to be the same as for DynamicTabNavigator, and I don't understand why they aren't)
in Profile. this.props: Object { "appMode": "WORK_MODE", "dispatch": [Function anonymous], "navigation": Object { "actions": Object { "dismiss": [Function dismiss], "goBack": [Function goBack], "navigate": [Function navigate], "pop": [Function pop], "popToTop": [Function popToTop], "push": [Function push], "replace": [Function replace], "reset": [Function reset], "setParams": [Function setParams], }, "addListener": [Function addListener], "dangerouslyGetParent": [Function anonymous], "dismiss": [Function anonymous], "dispatch": [Function anonymous], "getChildNavigation": [Function getChildNavigation], "getParam": [Function anonymous], "getScreenProps": [Function anonymous], "goBack": [Function anonymous], "isFocused": [Function isFocused], "navigate": [Function anonymous], "pop": [Function anonymous], "popToTop": [Function anonymous], "push": [Function anonymous], "replace": [Function anonymous], "reset": [Function anonymous], "router": undefined, "setParams": [Function anonymous], "state": Object { "key": "id-1547113035295-8", "routeName": "Profile", }, }, "screenProps": undefined, }
Questions regarding @dentemm's solution:
I a not sure how to implement your solution...
- Lets say that I have in my
TabRoutes
the three screens that you specified in your example - In my redux state I have a variable called 'appState'. If it is true I want to display first & second, if false first & third.
-
Here is the code that I wrote based on your example. I am not sure, however, which component is included in
CustomTabBar
. Can you elaborate?import React from 'react'; class CustomTabBar extends React.Component { render() { // a tab bar component has a routes object in the navigation state const { navigation } = this.props; // appState is extracted from redux state, see below if (this.props.appState) { return ( <View> <??? name='First' onPress={this.navigationHandler} focused={navigation.state.index === index} /> <??? name='Second' onPress={this.navigationHandler} focused={navigation.state.index === index} /> </View> ); } else { return ( <View> <??? name='First' onPress={this.navigationHandler} focused={navigation.state.index === index} /> <??? name='Third' onPress={this.navigationHandler} focused={navigation.state.index === index} /> </View> ); } } navigationHandler = (name) => { const {navigation} = this.props; navigation.navigate(name); } } const mapStateToProps = state => { const { appState } = state.app; return { appState }; }; export default connect(mapStateToProps)(CustomTabBar);
- Lets say that I have in my
-
Yossi over 5 yearsThanks @ValdaXD for the effort. The 1st solution gives the error: "Functions are not valid as a React child." The 2nd solution gives the error: "The component for route 'ProfileStack' must be a React component. For example: import MyScreen from './MyScreen'; ... ProfileStack: MyScreen, }"
-
Yossi over 5 yearsThanks. I will look at it in the next couple of days and add my comment.
-
Yossi over 5 yearsActually the second solution works, not using ProfileStack, but the component itself. I still have a problem showing the tab icon, but I believe that it can be solved. I suggest that you delete the first solution...
-
Yossi over 5 yearsI wasn't sure how to implement this, the other answer seems to work, though. Thanks
-
ValdaXD over 5 yearsOh ok i edited the answer to show hot to use navigationOptions wich each tab, as an alternative you can pass it based on the routename (RequestStack,etc...), an example is here [reactnavigation.org/docs/en/…, that way you use the default navigation options for that ;)
-
Yossi over 5 yearsThanks again, @ValdaXD , Works very well when, inside of 'tabs =', I specify the screen (e.g. Profile) and not the stack (e.g. ProfileStack that contains the Profile screen). If I use the stack, then I get the error: "TypeError: No "routes" found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child? See reactnavigation.org/docs/en/…". I read the text in the provided link, but couldn't figure out what to do. You probably will, as you seem like a react-navigation expert :)
-
ValdaXD over 5 yearsYeah, you also need to pass the navigation prop to every screen in stacknavigator [edit n2]. It's the problem with doing dynamic stuff , react-navigation handles that in the background in a normal navigator. As an alternative you can create 2 separate tab navigators and return them according to the conditions, or use redux state as the answer below, the first one is not really dynamic but it makes things easy, the second one requires a little bit of practice but it's worth it for doing dynamic stuff.
-
Yossi over 5 yearsHi @ValdaXD I thought that I finished bothering you... I implemented the navigator using "screen: <Profile navigation={this.props.navigation}". If I am using "navigation={props.navigation}" and I click on the button that invokes "this.props.navigation.openDrawer()", I get an error saying "this.props.navigation.openDrawer is not a function. If I am writing instead "navigation={this.props.navigation}" openDrawer does work, but this.props.navigation.navigate(<route-name>) doesn't. Any idea how to fix this?
-
Yossi over 5 yearsI am stuck with the other solution (still trying to make it work), trying to implement your solution. See my questions to you at the end of my question above.
-
dentemm over 5 yearsI updated my answer, you also create a CustomTabBarItem and filter on routeName when appState is true or false
-
Yossi over 5 yearsThanks for the prompt response, Highly Appreciated! Now I have some issues making it look like a default tab that is defined in navigation options. Do you have a full working example? (I see in SO that there are some issues, for example stackoverflow.com/questions/45099753/…)
-
dentemm over 5 yearsI created a non standard looking tab navigation, but the code is in a private repo of my employer. Which issues are you facing?
-
Yossi over 5 yearsNot sure how it works. I found an example (stackoverflow.com/questions/49071848/…) which helped you :) and I am not sure how and where routes are defined...
-
dentemm over 5 yearsThe routes prop is given automatically by react-navigation, and contains the screens you define in createBottomTabNavigator
-
ValdaXD over 5 yearsImplement the edit N 2 for the profilestack , i think i messed up when importing props, it shoud be in curly braces
({props})
,sorry ;-; -
Yossi over 5 yearsThanks. After trying both approaches, I think that I recommend the second one. Nevertheless, I am very grateful to your good will and efforts.