Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-Navigation 3: Open modal with createBottomTabNavigator and createStackNavigator

I know this question has been asked before, but only for older versions of react-navigation. Since then a few things have changed. createBottomTabNavigator makes it much faster to create a bottom navigator and the function jumpToIndex() doesn't exist anymore.

My Question is how to create an Instagram-like bottom tab, where the first, second, fourth and fifth navigation buttons act like usual tab navigators and the middle button (screen3) opens the modal screen3Modal.

I have tried it in react-navigation 3.x.x, using createBottomTabNavigator and createStackNavigator.

import React, { Component, } from 'react';
import { createBottomTabNavigator, createStackNavigator, createAppContainer, } from 'react-navigation';
import { Screen1, Screen2, Screen3, Screen4, Screen5 } from './screens';

const TabNavigator = createBottomTabNavigator({
  screen1: { screen: Screen1, },
  screen2: { screen: Screen2, },
  screen3: { 
    screen: () => null, 
    navigationOptions: () => ({
      tabBarOnPress: () => this.props.navigation.navigate('screen3Modal')
    })
  },
  screen4: { screen: Screen4, },
  screen5: { screen: Screen5, },
});

const StackNavigator = createStackNavigator({
  Home: { screen: TabNavigator },
  screen3Modal: { screen: Screen3, },
},
{
  initialRouteName: 'Home',
});

const StackNavigatorContainer = createAppContainer(StackNavigator);

export default class App extends Component {
  render() {
    return <StackNavigatorContainer />;
  }
}

This code creates the tab navigation and modal navigation. The modal can be opened from another screen, but it doesn't work from within the tab navigator. I get the errormessage undefined is not an object (evaluating '_this.props.navigation')

like image 390
Gabriel Winkler Avatar asked Feb 15 '19 09:02

Gabriel Winkler


4 Answers

I have found a relatively easy solution: Hide the original navigation bar with display:"none"

const TabNavigator = createBottomTabNavigator(
  {
    screen1: Screen1,
    screen2: Screen2,
    screen4: Screen4,
    screen5: Screen5,
  }, {
    tabBarOptions: {
      style: { display: "none", }
    }
  },
);

const StackNavigator = createStackNavigator(
  {
    Home: TabNavigator,
    screen3: Screen3
  }, {
    mode: 'modal',
  }
)

export default createAppContainer(StackNavigator);

And create a new navigation bar on each screen

<View style={{ flexDirection: "row", height: 50, justifyContent: "space-evenly", alignItems: "center", width: "100%" }}>
  <TouchableOpacity onPress={() => this.props.navigation.navigate("screen1")}><Text>1</Text></TouchableOpacity>
  <TouchableOpacity onPress={() => this.props.navigation.navigate("screen2")}><Text>2</Text></TouchableOpacity>
  <TouchableOpacity onPress={() => this.props.navigation.navigate("screen3")}><Text>3</Text></TouchableOpacity>
  <TouchableOpacity onPress={() => this.props.navigation.navigate("screen4")}><Text>4</Text></TouchableOpacity>
  <TouchableOpacity onPress={() => this.props.navigation.navigate("screen5")}><Text>5</Text></TouchableOpacity>
</View>
like image 178
Gabriel Winkler Avatar answered Oct 19 '22 05:10

Gabriel Winkler


You can wrap everything inside the same StackNavigator, that way you can navigate to other routes easily. Here i'm passing the screen3 as default route, but you can change that to whatever you'd like.

import React, { Component, } from 'react';
import { createBottomTabNavigator, createStackNavigator, createAppContainer, } from 'react-navigation';
import { Screen1, Screen2, Screen3, Screen4, Screen5 } from './screens';

const TabNavigator = createBottomTabNavigator({
  screen1: { screen: Screen1, },
  screen2: { screen: Screen2, },
  screen3: { screen: () => null, }, //this.props.navigation.navigate('screen3Modal')
  screen4: { screen: Screen4, },
  screen5: { screen: Screen5, },
});

const StackNavigator = createStackNavigator({
  Home: { screen: TabNavigator },
  screen3Modal: { screen: Screen3, },
},
{
  initialRouteName: 'screen3Modal',
});

const StackNavigatorContainer = createAppContainer(StackNavigator);

export default class App extends Component {
  render() {
    return <StackNavigatorContainer />;
  }
}
like image 22
kivul Avatar answered Oct 19 '22 05:10

kivul


Well I have spent hours upon hours to solve this problem.

Here's fully working & tested solution:

  1. Set up your app container by including tabs stack & to be opened modal navigation stack:
  const FinalTabsStack = createStackNavigator(
    {
      tabs: TabNavigator,
      screen1: Screen1Navigator,
    }, {
      mode: 'modal',
    }
  )
  1. Create app container with that tabs stack per this guide

  2. Inside the TabNavigator in the createBottomTabNavigator return null component for specific tab (screen3) (to turn off navigation by react-navigator) and handle the tab manually inside the defaultNavigationOptions by creating custom component for it.

  const TabNavigator = createBottomTabNavigator({
    screen1: Screen1Navigator,
    screen2: Screen2Navigator,
    screen3: () => null,
    screen4: Screen4Navigator,
    screen5: Screen5Navigator,
}
    defaultNavigationOptions: ({ navigation }) => ({
      mode: 'modal',
      header: null,
      tabBarIcon: ({ focused }) => {
        const { routeName } = navigation.state;
        if (routeName === 'screen3') {
          return <Screen3Tab isFocused={focused} />;
        }
      },
    }),
  1. Handle click manually inside a custom tab component Screen3Tab with TouchableWithoutFeedback & onPress. Inside Screen3Tab component:
  <TouchableWithoutFeedback onPress={this.onPress}>
    <Your custom tab component here />
  </TouchableWithoutFeedback>

  1. Once you catch onPress dispatch redux event
  onPress = () => {
    this.props.dispatch({ type: 'NAVIGATION_NAVIGATE', payload: {
      key: 'screen3',
      routeName: 'screen3',
    }})
  }
  1. Handle dispatched event using Navigation Service. I'm using redux-saga for it
  NavigationService.navigate(action.payload);

A bit complicated but works.

like image 24
Oleg Dater Avatar answered Oct 19 '22 04:10

Oleg Dater


I've found a better solution based on this github issue. You just have to add in the specific tab configuration, in your case screen3 the event navigationOptions(you already had it), you were pretty close, but, you had to receive the navigation as a parameter, because there isn't any context for this as you were using it. To correct the first code that you wrote I would change it to this and it would work:

navigationOptions: ({ navigation }) => ({
  tabBarOnPress: ({ navigation }) => {
    navigation.navigate("screen3Modal");
  }
})
like image 25
Brandon Aguilar Avatar answered Oct 19 '22 05:10

Brandon Aguilar