How to create dynamic tabs in React Navigation v3 - reactjs

I have stack navigator wrapped with app container:
const AppNavigator = createStackNavigator({
Home: {
screen: Home,
},
});
export default createAppContainer(AppNavigator);
Home should have dynamic tabs. I want to load some info from backend and then generate tabs.
And here is my Home component:
class Home extends Component {
get tabs() {
return {
Main: { screen: Demo },
World: { screen: Demo },
};
}
get tabOptions() {
return {
// options...
};
}
render() {
const Tabs = createMaterialTopTabNavigator(this.tabs, this.tabOptions);
return <Tabs />;
}
}
In this case I am getting this error:
But if I am wrapping with createAppContainer...
const Tabs = createAppContainer(createMaterialTopTabNavigator(this.tabs, this.tabOptions));
...then I am getting warning about more than one container in app.
So how to make dynamic tabs in a right way?
UPD 1. Real code of getting tabs which I use now with yellow warning.
get tabs() {
const { categories } = this.props;
return reduce((acc, item) => assoc(prop('name', item), Demo, acc), {})(categories); // ramda
}

That error shows up because wrapping a navigator as a component is antipattern since the navigation prop is being broken that way.
instead, don't use a class , use plain functions, or you are gonna struggle so much like the other guys that tried to use a component
function tabs(){
return {
Main: { screen: Demo },
World: { screen: Demo },
}
}
function tabOptions(){
return {
// options...
};
}
export default createMaterialTopTabNavigator(tabs(), tabOptions())

Related

undefined is not an object(evaluating 'this.props.navigation.navigate')

Can someone help me? I keep getting the error (See title).
I know there are a lot of people asking this question before in StackOverflow. I read their answers but when I paste the solution, I still keep hiting this error.
Below are the code.
Thank you very much.
//Jimmy code
import { Navigation } from 'react-native-navigation';
import { StackNavigator } from 'react-navigation';
import { Merchant2 } from "./../Merchant2/Merchant2";
export class Login extends React.Component{
constructor(prop) {
super(prop);
Navigation.events().bindComponent(this);
this.state = {
userName: "",
password: "",
isVerify: false
};
this.onPress=this.onPress.bind(this);
}
onPress = () => {
//const { navigate } = this.props.navigation;
navigate('Page2');
alert(1)
}
render() {
navigationOptions = {
title: 'Results',
};
return (
<View style={[styles.formFooter]}>
<TouchableHighlight style={[commonStyles.alignItemsCenter, styles.loginBtn]}
activeOpacity={1}
underlayColor={'#cdcdcf'}
onPress={this.onPress.bind(this)}
>
);
};
}
const App = StackNavigator({
Home: { screen: Merchant2 },
});
export default App;
A few things here:
You should use methods with names different than the native ones such as 'onPress'
If you don't have a navigation props, it's simply because you never references it in your stack navigation. All items who's view is specified in a navigation object should have a navigation props.
navigationOptions should be tagged static if you define it in your component or like Login.navigationOptions. Also, if you want to add navigation options from the action of elements you create in your navigation options, you can do this like (in your component) : static navigationOptions = ({navigation}) => ({your navigation options here})

React Native Tab Navigation nested in Stack Navigation How To?

I am new to react native, and have come across this issue with navigation. I have a 'main menu' (TabNav) that I want to render on every page.
I have some pages that are not accessible from the main menu (TabNav), but should still have the menu options. I'm not sure how to impliment this. Here is the code I have so far:
const TabNav = createMaterialBottomTabNavigator({
Map: { screen: HomeScreen },
Details: { screen: Details },
});
const extraNav = createStackNavigator(
{
SinglePage: SinglePage,
Tabs: TabNav
}
);
const RootStackNav = createStackNavigator(
{
Tabs: {
screen: TabNav
},
SinglePage: {
screen: extraNav,
}
},
);
I navigate to the single separate page via:
render() {
const { navigation } = this.props;
return (
<View>
<Icon
name='place'
onPress={() => navigation.navigate('SinglePage')}
/>
</View>
);
}
What I want to happen is when I navigate to the single separate page my "main menu" is still present.
Right now I can navigate to all the pages, but on the single separate page the tabs are not available, and I have to hit 'back' to access it again.
You can use this code to nest a stack navigator to a tab navigator
const RootTabNav = createMaterialBottomTabNavigator({
Map: { screen: HomeScreen },
DetailStack: { screen: stackNav},
});
const stackNav= createStackNavigator(
{
SinglePage: { screen: SinglePage}
Details: { screen: Details },
}
);
Its just an example of nesting stack to tabNavigator. I hope this may help you.

React-native nested navigators within Redux

I've decided that I need to swap my regular react-nagivation setup for a redux container, mostly because I need to keep track of stuff like login status etc.
I won't lie that since it's my first react navigation moving it into redux is a hell lot of confusion for me, but step by step I've got to the point where my basic structure is working, well... almost working.
What I have is a StackNav on top of everything, with the main screen being a TabNav. The screen is loading fine, and parts of the app that switch to different screen of that top-level StackNav are working fine. My problem occurs when I switch from 1st tab to the 2nd within that TabNav.
To clarify, I've got 2 tabs - 'Home' and 'My Account'.
Switching from home to my account does look like tabs are being switched, but the component is not being rendered properly. It's missing all that's within the render function for it together with the styles. Then, when I want to switch back to 'Home' tab, the screen slides like I would actually switch between top-level StackNav, instead of just switching between tabs. On top of that, Now after being back to 'Home' tab, I've got the option to 'go back' in the top left corner which when used, just slides to the 'previous' view of Home, even tho it's the same tab.
I'm confused while writing that but hopefully someone will be able to see sense in that. Here's my code:
top level Stack Nav config:
const routeConfiguration = {
Home: { screen: HomeTabsNavigation },
GameList: { screen: GameList },
OfferDetails: { screen: OfferDetails },
}
const stackNavOptions = {
initialRouteName: 'Home',
mode: 'card',
headerMode: 'float',
}
export const StackNavConfig = StackNavigator(routeConfiguration, stackNavOptions);
top level Stack Nav component:
const mapStateToProps = (state) => {
return {
navigationState: state.stackNav,
}
}
class StackNavigation extends React.Component {
render() {
const { dispatch, navigationState } = this.props
return (
<StackNavConfig
navigation={
addNavigationHelpers({
dispatch,
state: navigationState,
})
}
/>
)
}
}
export default connect(mapStateToProps)(StackNavigation)
HomeTabs defined as the first screen of top-level StackNav:
const HomeTabs = TabNavigator({
Home: { screen: HomeView },
MyAccount: { screen: MyAccountView },
}, {
tabBarOptions: tabBarOptions,
});
export default HomeTabs;
HomeTabs component:
const mapStateToProps = (state) => {
return {
navigationState: state.homeTabs,
}
}
class HomeTabsNavigation extends Component {
render() {
const { navigationState, dispatch } = this.props;
return (
<HomeTabs
navigation={
addNavigationHelpers({
dispatch,
state: navigationState,
})
}
/>
);
}
}
export default connect(mapStateToProps)(HomeTabsNavigation);
My Redux store:
const middleware = () => {
return applyMiddleware(createLogger());
}
export default createStore(
combineReducers({
stackNav: (state, action) => StackNavConfig.router.getStateForAction(action, state),
homeTabs: (state, action) => HomeTabs.router.getStateForAction(action, state),
}),
middleware(),
)
My TabNav views are just displaying text for now so I won't put them here unless it's important, then I'll of course update the code.
Help! ;)
Ok so I've found the issue. The problem was that I had same route name defined in both top level Stack Navigator and nested Tab Navigator.
As you can see here:
const routeConfiguration = {
Home: { screen: HomeTabsNavigation },
GameList: { screen: GameList },
OfferDetails: { screen: OfferDetails },
}
const stackNavOptions = {
initialRouteName: 'Home',
mode: 'card',
headerMode: 'float',
}
export const StackNavConfig = StackNavigator(routeConfiguration, stackNavOptions);
and here:
const HomeTabs = TabNavigator({
Home: { screen: HomeView },
MyAccount: { screen: MyAccountView },
}, {
tabBarOptions: tabBarOptions,
});
export default HomeTabs;
It seems that these routes are maybe merged together at some point and whenever my Tab Nav was trying to go 'back' to his 'Home' screen it was the top-level Stack Nav taking over the control and loading it's own 'Home' screen on top of what was there before, creating exactly what it is which is a Stacked View of screens. I've renamed it to 'TabsHome' and everything is working fine. Solved!

React Native: Passing props to nested navigation

I'm using the slick React Navigation and following the nested navigation recipe here, but i don't know how to pass 'this' to my navigation. Sorry for my ignorance.
Here is my general structure outline:
class MyApp extends Component {
render() {
return (
<StackNavigation
screenProps={this.state}
/>
)
}
}
const MainScreenNavigator = TabNavigator(
{
Awesome: { screen: Awesome } // How do I pass this.state?
}
)
const routesConfig = {
Home: { screen: MainScreenNavigator },
Profile: { screen: Profile }
}
const StackNavigation = StackNavigator(routesConfig, {initialRouteName: 'Home'})
So how do I pass this.state to my MainScreenNavigator?
According to this, you can pass an extra option to the StackNavigator in the StackNavigatorConfig. Something like this:
const StackNavigation = StackNavigator(routesConfig, StackNavigatorConfig)
... where the StackNavigatorConfig is an object like:
{ initialRouteName: 'Home', initialRouteParams: {...this.state} }
And that is gonna pass all your state to the initial route of your navigator.
For any other route you can do:
this.props.navigation.navigate('SomeScreen', {<the-greatest-object-for-pass-down>})
And that is gonna pass the-greatest-object-for-pass-down to SomeScreen.
Finally, you can access to that params for both (the initial and any other screen) with this.props.navigation.params.
not sure if you can pass the state as-is. However the params you passed can be accessed by the next screen as follows.. you can try this
this.props.navigation.state.params.<<property passes>>

How to get current navigation state

I am using React Navigation's Tab Navigator from https://reactnavigation.org/docs/navigators/tab, when I switch between the Tab Screens I don't get any navigation state in this.props.navigation.
Tab Navigator:
import React, { Component } from 'react';
import { View, Text, Image} from 'react-native';
import DashboardTabScreen from 'FinanceBakerZ/src/components/dashboard/DashboardTabScreen';
import { TabNavigator } from 'react-navigation';
render() {
console.log(this.props.navigation);
return (
<View>
<DashboardTabNavigator />
</View>
);
}
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
},
THISWEEK: {
screen: DashboardTabScreen
}
});
DASHBOARD SCREEN:
import React, { Component } from 'react';
import { View, Text} from 'react-native';
export default class DashboardTabScreen extends Component {
constructor(props) {
super(props);
this.state = {};
console.log('props', props);
}
render() {
console.log('props', this.props);
return (
<View style={{flex: 1}}>
<Text>Checking!</Text>
</View>
);
}
}
I get props at Dashboard Screen when it renders the component first but I don't get props when I switch the tabs.
I need to get the current Screen name i.e TODAY or THISWEEK.
Your problem is about "Screen Tracking", react-navigation has an officially guide for this. you can use onNavigationStateChange to track the screen by using built-in navigation container or write a Redux middleware to track the screen if you want to integrate with Redux. More detail can be found at the officially guide: Screen-Tracking. Below is a sample code from the guide by using onNavigationStateChange:
import { GoogleAnalyticsTracker } from 'react-native-google-analytics-bridge';
const tracker = new GoogleAnalyticsTracker(GA_TRACKING_ID);
// gets the current screen from navigation state
function getCurrentRouteName(navigationState) {
if (!navigationState) {
return null;
}
const route = navigationState.routes[navigationState.index];
// dive into nested navigators
if (route.routes) {
return getCurrentRouteName(route);
}
return route.routeName;
}
const AppNavigator = StackNavigator(AppRouteConfigs);
export default () => (
<AppNavigator
onNavigationStateChange={(prevState, currentState) => {
const currentScreen = getCurrentRouteName(currentState);
const prevScreen = getCurrentRouteName(prevState);
if (prevScreen !== currentScreen) {
// the line below uses the Google Analytics tracker
// change the tracker here to use other Mobile analytics SDK.
tracker.trackScreenView(currentScreen);
}
}}
/>
);
Check all properties first, like
<Text>{JSON.stringify(this.props, null, 2)}</Text>
Above json array will show you current state of navigation under routeName index i.e.
this.props.navigation.state.routeName
Have you tried to define navigationOptions in your route object?
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: {
title: 'TODAY',
},
},
})
You can also set navigationOptions to a callback that will be invoked with the navigation object.
const DashboardTabNavigator = TabNavigator({
TODAY: {
screen: DashboardTabScreen
navigationOptions: ({ navigation }) => ({
title: 'TODAY',
navigationState: navigation.state,
})
},
})
Read more about navigationOptions https://reactnavigation.org/docs/navigators/
Answer as of React Navigation v6
Depending on whether you want to trigger re-renders on value changes:
const state = navigation.getState();
or
const state = useNavigationState(state => state);
Reference:
https://reactnavigation.org/docs/use-navigation-state#how-is-usenavigationstate-different-from-navigationgetstate

Resources