How to selectively show modals in React Navigation? - react-native

I'm building an app that is very similar to the Calendar app on iOS. I have a tab-based app that should sometimes trigger modals and other times cards, based on the screen being shown. In Apple's Calendar app, if you click on an event, you'll get a normal (slide from side or "card") transition, but if you click on the "+" button, you'll get a modal (bottom up) transition. I'd like to achieve something similar. The problem now is I can only get either all modals or all card transitions.
const CalendarTab = createStackNavigator(
{
CalendarView: {screen: CalendarView},
ViewEvent: {screen: ViewEvent}, // This should be a "card" transition
AddEvent: {screen: AddEvent}, // This should be a "modal" transition
},
{
mode: "modal", // This shouldn't be hard-coded
initialRouteName: "CalendarView",
}
);
const TabStack = createBottomTabNavigator({
Calendar: CalendarTab,
Settings: SettingsTab,
},
{
initialRouteName: "Calendar",
navigationOptions: ({ navigation }) => ({
tabBarVisible: navigation.state.index === 0, // This should only apply to modals
});
}
export default class App extends Component {
render() {
return <TabStack />;
}
}
Edit: I did find a partial solution here: https://github.com/react-navigation/react-navigation/issues/707#issuecomment-299859578, but it causes the headers to get messed up and is generally a brittle solution.

Related

React memo with navigation drawer in react native

I am using navigation drawer in react native & on dashboard initial route i have javascript graph & when i navigate to other pages & come back i want to optional refresh it. It depends from which page user is coming from for example if user navigates to detail page & come back graph should not refresh & incase user navigates to come other screen like help screen & come back it should not refresh.
I found a way to not the refresh routes were to put unmountInactiveroutes in drawer options, but then all routes where not refresh which was hard to maintain.
Another option i tried using memo to remember dashboard component state but it is not working.
Any clue will be appreciated.
const Drawer = createDrawerNavigator({
Home: {
screen: Home
},
.....
SearchResultPage :{
screen: SearchResultPage
}
}, {
initialRouteName: AppConstants.NAV_HOME,
//unmountInactiveRoutes: true,
navigationOptions: {
headerVisible: false
},
headerMode: AppConstants.NONE,
contentComponent: props => <Sidebar {...props}/>
})
const AppNavigator = createStackNavigator({
Drawer: {
screen: Drawer,
navigationOptions: {
header: null
}
}
}, {
initialRouteName: AppConstants.NAV_DRAWER,
//unmountInactiveRoutes: true,
headerMode: AppConstants.NONE
})
let AppContainer = createAppContainer(AppNavigator);

React Native and React Navigation — How to get screen title to show in header and bottom tab navigator to also show

I am teaching myself React Native and building a fairly simple app,irMobile.
I've set up each screen with (using the About screen as an example)
static navigationOptions = () => ({
title: 'About'
})
and in my app's main component
const MainNavigator = createBottomTabNavigator({ About })
const StackNavigator = createStackNavigator({ MainNavigator })
const AppContainer = createAppContainer(StackNavigator)
then in the component itself
render() {
return (
<View style={styles.container}>
<AppContainer />
</View>
)
}
My screens render with a nice nav bar at the bottom but I don't get any titles in the header, just an empty space.
I've gone through the docs several times looking to see what I have missed and I'm stumped.
If I use the inspector in the iOS simulator I can select the header and it shows it to be a View at x: 0, y: 44 with width: 375 and height: 43.7, so the thing is there I just can't see it.
I've tried setting a headerStyle to { backgroundColor: 'red' } to see if I can at least see that, but no.
However, if I change the StackNavigator to put the About screen in there along with the MainNavigator as follows:
const StackNavigator = createStackNavigator({ About, MainNavigator })
Then I get my header showing up as expected, but the bottom tab bar no longer shows.
So that's confusing.
The docs are a bit vague on how to get both a header and a bottom tab navigator into the same app. Clearly, being a bit of a React Native noob here, I am missing something really obvious and simple.
What am I failing to understand and how do I fix this?
Source code at github.com/davesag/irMobile.
By setting navigationOptions inside the component, you apply it to the navigation element that is calling the component.
So in your case, the title you define in About applies to the bottom tab, and not to the screen as you would like to.
Rather than setting navigationOptions as a static property inside the component, you need to define it directly when creating the stack navigation.
In addition to this, I think you want to nest the stack inside the bottom tab navigation, not the other way around. By doing so, you can have a different title for each screen.
Here is an example:
const AboutStack = createStackNavigator({ About: { screen: About, navigationOptions: { title: 'About Title' } } });
const CreditStack = createStackNavigator({ Credit: { screen: Credit, navigationOptions: { title: 'Credit Title' } } });
const MainNavigator = createBottomTabNavigator({
About: { screen: AboutStack, navigationOptions: { title: 'About Label' } },
Credit: { screen: CreditStack, navigationOptions: { title: 'Credit Label' } },
});
const AppContainer = createAppContainer(MainNavigator);
Like this, the header title will be "About Title" and the tab label "About Label".
Okay I have worked it out.
The trick is to use getActiveChildNavigationOptions.
So now my code looks like
const navBar = useStorybook({ Balances, Settings, About })
const MainNavigator = createBottomTabNavigator(navBar, {
navigationOptions: ({ navigation, screenProps }) =>
getActiveChildNavigationOptions(navigation, screenProps)
})
const StackNavigator = createStackNavigator({ MainNavigator })
const AppContainer = createAppContainer(StackNavigator)
As #Remeus pointed out, the titles in my actual screen components were only making it to the MainNavigator and not accessible by the StackNavigator.
Rather than invert my stack however and create a StackNavigator for each tab, it seems to me to be cleaner to leverage getActiveChildNavigationOptions to grab the screen components' title (and other) navigationOptions.
This works perfectly.

this.props.navigation vs NavigationActions in react-navigation

NavigationActions supported below actions but I can't understand what is the usage of each one when we have access to helper functions with this.props.navigation.
navigate
reset
back
init
so why we have to import NavigationActions?
Thanks in Advance.
They all have lots of usage but let me give you couple of examples.
Example 1 setParams
Lets say you have a StackNavigator with 2 screens.
const Navigator = StackNavigator({
Home: { screen: Home },
Items: { screen: Items },
Profile: { screen: Profile }
});
Now lets say you navigated to Items screen and then to Profile screen and you are listing Items according to this.props.navigation.state.params.type. Lets say this type can be set through Profile screen.
When user changed the setting of type you need to re-render Items screen. But when you do this.props.navigation.goBack() from Profile screen, Items screen will not re-render unless you do some change on props or state.
At this position setParams action required. Although you can use this.props.navigation.setParams on Profile screen it will set params for the current screen.
NavigationActions lets you set params for other screens with key property of those screens.
Example 2 reset and navigate
In your app at some point (logout, refresh, notification etc.) you might want to reset the navigation stack and then recreate the stack with the desired position.
When the user receives a notification it is the expected behavior to redirect to the related post/article/message and when user wants to goBack user should redirect to the correct screen. At this point you can simulate the users navigation with combination of reset and navigate actions.
There are a lot more use cases of these actions. But at the end, if you don't need them you don't have to import them. At some point if you need them you will understand how to use them more.
Besides what #bennygenel said, NavigationActions also good to navigate in nested stacks.
For example navigating from a one screen to a screen in different stack.
Example:
index.js
const StackOpportunityNavigator = createStackNavigator(
{
MainOpportunity: OpportunityScreen,
SecondScreen: SecondScreen
},
{
initialRouteName: 'MainOpportunity',
headerMode: 'none',
transitionConfig: () => fromLeft(600)
}
);
const DrawerNavigationOptions = createDrawerNavigator(
{
OpportunityStack: { screen: StackOpportunityNavigator },
History: HistoryScreen
},
{
contentComponent: props => <SideBar {...props} />,
drawerPosition: 'right',
headerMode: 'none',
transitionConfig: () => fromLeft(600)
}
);
const LoginNavigator = createStackNavigator(
{
LoadingScreen: LoadingScreen,
LoginScreen: LoginScreen,
DrawerNavigator: DrawerNavigationOptions
},
{
transitionConfig: () => fromLeft(600),
navigationOptions: {
header: props => <HeaderBar {...props} />
}
}
);
export default LoginNavigator;
Now i can from a LoginScreen.js navigate to MainScreen(OpportunityScreen.js), by writing the whole nested navigation path that needed to be done from one screen to another:
const navigateToMainScreen = NavigationActions.navigate({
routeName: 'DrawerNavigator',
action: NavigationActions.navigate({
routeName: 'OpportunityStack',
action: NavigationActions.navigate({
routeName: 'MainOpportunity'
})
})
});
_this.props.navigation.dispatch(navigateToMainScreen);
Hope it helps :)
Every one of those helper functions map into navigation.dispatch(action), with a different action object from NavigationActions.
There are a few instances where you still need to use them, for example, when using navigate, you can change the navigation hierarchy on more than one level by specifying a sub action to happen on the navigator that you just routed to.
this.props.navigation.navigate("some-root", {param: "value"}, someOtherAction)
Where someOtherAction can be any action object.
You can find more information here.

How to navigate to different screen and get back to the screen from where its navigated using DrawerNavigator?

I have two components (List and Detail):
List.js:
export default class List extends React.Component {
render() {
const {navigate} = this.props.navigation;
return (
<View style={styles.container}>
<Text style={styles.headerText}>List of Contents</Text>
<Button onPress={()=> navigate('Detail')} title="Go to details"/>
</View>
)
}
}
Detail.js:
export default class Detail extends React.Component {
render() {
return (
<View>
<Text style={styles.headerText}>Details of the content</Text>
</View>
)
}
}
I would like to have two screens (NowList and SoonList) which are basically the same type of list, so I am using the same List component for both the screen. And from each of these screens I want to navigate to the Details of the item, also which in this case has the same type of layout so I am using Detail component for both the list items.
Finally, when the App starts I want the NowList screen to show. And using the drawer I would like to navigate to SoonList screen.
I am not sure how to configure this route. But, this is how I have configured the routes for now:
const NowStack = StackNavigator({
NowList: {
screen: List,
navigationOptions: {
title: 'Now',
}
},
Detail: {
screen: Detail,
navigationOptions: {
title: 'Detail',
}
}
});
const SoonStack = StackNavigator({
SoonList: {
screen: List,
navigationOptions: {
title: 'Soon',
}
}
});
export const Drawer = DrawerNavigator({
Now: {
screen: NowStack,
},
Soon: {
screen: SoonStack,
}
});
When I navigate from NowList route to Detail route. There's a back button in the Detail route which on press navigates back to the NowList.
However, when I go to Soon route, and navigate to Detail route, I go to Detail screen. But, when I press the back button on Detail navigation header, instead of navigating back to the SoonList screen, I am navigated to the NowList screen.
I think I am missing something here, or my route layout is not how its suppose to be. Could you help me how to configure the routes so that I can use DrawerNavigator to navigate to different screens, and from those screens navigate to another screen and again back to the screen navigated from?
You can make a stack navigator that contain your drawer navigator and detail, this way you can access both now list and soon list from drawer and able to navigate to detail screen from both list.
const App = StackNavigator({
Drawer: {
screen: Drawer,
},
Detail: {
screen: Detail,
navigationOptions: {
title: 'Detail',
},
},
});
const Drawer = DrawerNavigator({
NowList: {
screen: List,
},
SoonList: {
screen: List,
},
});
Your route layout doesn't specify a Detail screen in the SoonStack navigator. When you navigate to Detail, react-navigation navigates to the only screen that is named that way. Since it is in the NowStack, going back returns to the first screen in the stack.
To solve this, you can add another Detail screen to the SoonStack navigator, however you'd want to either name it differently, or navigate to each Detail screen using its key.
In order to be able to navigate to the right Detail screen, you can add a parameter to each stack navigator.
For example:
const SoonStack = StackNavigator({
SoonList: {
screen: List,
navigationOptions: {
title: 'Soon',
}
},
SoonDetail: {
screen: Detail,
navigationOptions: {
title: 'Detail',
}
}
},{
initialRouteParams: {
detailScreen: 'SoonDetail'
}
});
You can then use the parameter to navigate to the correct screen.
Also, it's possible to only use a single StackNavigator with the three distinct screens in it, and reset the stack to the correct screen when switching with the drawer.
You can read more about reset here.
how to use reset
If you have a single stack navigator:
const TheStack = StackNavigator({
NowList: { screen: List, ... },
SoonList: { screen: List, ... },
Detail: {screen: Details, ... }
});
Then the drawer can reset the stack state to be whatever you want. For example, if the first screen was NowList and SoonList is clicked in the drawer, you can call:
const action = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({routeName: 'SoonList'});
]
});
this.props.navigation.dispatch(resetAction);
This would cause the stack to reset and have the SoonList as the first screen. If Details is opened, then it is the second in the stack, and back will always go back to the correct screen.

How do i make a TabNavigator button push a modal screen with React Navigation

Using the React Navigation tab navigator https://reactnavigation.org/docs/navigators/tab how do I make one of the tab buttons push the screen up as a full screen modal? I see the stack navigator has a mode=modal option. how do I get that mode to be used when clicking on the TakePhoto tab button? Clicking on it currently still shows the tab bar on the bottom.
const MyApp = TabNavigator({
Home: {
screen: MyHomeScreen,
},
TakePhoto: {
screen: PhotoPickerScreen, // how can I have this screen show up as a full screen modal?
},
});
Actually, there is no support in react-navigation to change the way of presentation on the fly from default to modal (see the discussion about this here). I ran into the same issue and solved it by using a very top StackNavigator with headerMode set to none and mode set to modal:
const MainTabNavigator = TabNavigator(
{
Tab1Home: { screen: Tab1Screen },
Tab2Home: { screen: Tab2Screen }
}
);
const LoginRegisterStackNavigator = StackNavigator({
Login: { screen: LoginScreen }
});
const ModalStackNavigator = StackNavigator({
MainTabNavigator: { screen: MainTabNavigator },
LoginScreenStackNavigator: { screen: LoginRegisterStackNavigator }
}, {
headerMode: 'none',
mode: 'modal'
});
This allows me to do the following (using redux) in Tab1Screen and Tab2Screen to bring up the modal view from wherever I want:
this.props.navigation.navigate('LoginScreenStackNavigator');
Not sure if this is still relevant for you, but i've managed to find away to achieve this.
So i've managed to get it working by using the tabBarComponent inside the tabNavigatorConifg, you can stop the tab navigation from navigating depending on the index.
tabBarComponent: ({jumpToIndex, ...props, navigation}) => (
<TabBarBottom
{...props}
jumpToIndex={index => {
if (index === 2) {
navigation.navigate('camera')
}
else {
jumpToIndex(index)
}
}}
/>
)
Once you've done this, my method of showing the view modally on top of the tab views was to put the tabnavigator inside of a stacknavigatior and then just navigate to a new screen inside of the stacknavigator.
react-navigation's bottomTabNavigator has a tabBarOnPress navigation option you can use to override tab presses:
https://reactnavigation.org/docs/en/bottom-tab-navigator.html#tabbaronpress
const AppContainer = createStackNavigator(
{
default: createBottomTabNavigator(
{
TAB_0: Stack0,
TAB_1: Stack1,
TAB_2: Stack2,
TAB_3: View // plain rn-view, or any old unused screen
},
{
defaultNavigationOptions: {
// other tab navigation options...
tabBarOnPress: ({ navigation, defaultHandler }) => {
if (navigation.state.key === 'TAB_3') {
navigation.navigate('tabToOpenAsModal');
} else {
defaultHandler();
}
}
}
}
),
tabToOpenAsModal: {
screen: TabToOpenAsModalScreen
}
},
{
mode: 'modal',
headerMode: 'none'
}
);
If you nest your tab navigator within a stack navigator with a modal, you can open this when the tab bar button is pressed. Since the modal was opened and not the tab, when the modal is closed the app returns to the screen that was visible before the modal tab was pressed.
One way to make the tab bar go away is to hide the tabBar with visible: false:
const MyApp = TabNavigator({
Home: {
screen: MyHomeScreen,
},
TakePhoto: {
screen: PhotoPickerScreen,
navigationOptions: {
tabBar: {
visible: false,
},
},
},
});
However, that does not seem to trigger any transition to fullscreen, which I guess is desired?
Another option could be to wrap PhotoPickerScreen inside a new StackNavigator and set that navigator to mode='modal'.
You might have to trigger the navigation to that modal from onPress on the tabItem somehow (eg. navigation.navigate('TakePhoto').)
Note, I'm trying to wrap my head around how best to structure navigation myself, so …
Third option, implementing a StackNavigator as parent, then adding the MyApp TabNavigator as the first route inside of it, could be the most flexible solution. Then the TakePhoto screen would be on the same level as the TabNavigator, allowing you to route to it from wherever.
Interested to hear what you come up with!
I suggest two solution
First one is about hide it
For seconde one please read that: https://reactnavigation.org/docs/hiding-tabbar-in-screens
<Tab.Screen
name={Routes.CREATE_ANNOUNCEMENT_SCREEN}
// name={Routes.TEST_SCREEN}
options={{
.
.
.
tabBarVisible: false, <----- solution 1
tabBarButton: (props) => ( <----- or solution 2
<TouchableOpacity
{...props}
onPress={() => {
navigation.navigate(Routes.DETAILS_SCREEN);
}}
/>
),
}}
component={CreateAnnouncementScreen}
/>
Docs are patchy in some areas, but here it is:
export default class HomeScene extends Component {
static navigationOptions = {
title: 'foo',
header:{
visible: true
}
}
....
}