Drawer Navigator with shared header among all children - react-native

This is about a React Native + React Navigation issue.
In my project I need a top-level DrawerNavigator so as a shared header among all screens, so that when the drawer opens it stays on top of the header. Best solution I've come up so far was a DrawerNavigator with all of its routes as a StackNavigator with same header config, but that just seems to hacky, ugly and non-performatic, as I think the header would be re-rendering everytime. Has anyone done anything similar? Thanks in advance.
Router.js
export const RegisterStack = StackNavigator(
{
Register: {screen: Register},
},
{
mode: 'modal',
navigationOptions: {
title: 'My title',
}
}
);
export const HistoryStack = StackNavigator(
{
History: {screen: History},
},
{
mode: 'modal',
navigationOptions: {
title: 'My title',
}
}
);
export const FavoritesStack = StackNavigator(
{
Favorites: {screen: Favorites},
},
{
mode: 'modal',
navigationOptions: {
title: 'My title',
}
}
);
export const CardsStack = StackNavigator(
{
Cards: {screen: Cards},
},
{
mode: 'modal',
navigationOptions: {
title: 'My title',
}
}
);
export const AgreementStack = StackNavigator(
{
Agreement: {screen: Agreement},
},
{
mode: 'modal',
navigationOptions: {
title: 'My title',
}
}
);
export const createRootNavigator = () => {
return DrawerNavigator({
Register: {
screen: RegisterStack,
},
History: {
screen: HistoryStack,
},
Favorites: {
screen: FavoritesStack,
},
Cards: {
screen: CardsStack
},
Agreement: {
screen: AgreementStack,
},
})
};
App.js
export default class App extends Component {
render() {
const Layout = createRootNavigator();
return (
<Provider store={store}>
<Layout style={styles.main}/>
</Provider>
);
}
}

Okay I actually got this to work, what you have to do is make a stackNavigator inside the drawer navigator, the drawerNavigator is able to see inside the stackNavigator and navigate between the stacknavigator screens. Using the contentComponent. Once you have done this you can put the shard header inside the stackNavigator.
const PrimaryDrawer = createDrawerNavigator({
PrimaryStack,
}, {
contentComponent: PrimaryDrawerContentComponent,
})
const PrimaryStack = createStackNavigator({
Feed,
Home,
}, {
})
import _ from 'lodash'
export default class PrimaryDrawerContentComponent extends Component {
render() {
const { navigation } = this.props
return (
<FlatList
data={_.keys(navigation.router.childRouters.PrimaryStack.childRouters)}
renderItem={({ item, index }) => (
<DrawerItem
routeName={item}
navigate={navigation.navigate}
icon={icons[index] ? icons[index] : null}
/>
)}
keyExtractor={item => item.key}
/>
)
}
}
const DrawerItem = ({ routeName, navigate, icon }) => {
return (
<TouchableOpacity
style={styles.button}
onPress={() => { navigate(routeName) }}
>
<Image
source={icon}
/>
<Text style={styles.text}>{routeName}</Text>
</TouchableOpacity>
)
}

Related

How do I conditionally render a header in a Stack Navigator for React Native Navigation?

I would like my initial page to NOT contain a header. However, I would like a header to appear on each subsequent page. Right now my current stackNavigator looks like this:
const AppNavigator = createStackNavigator(
{
HomeScreen: HomePage,
SecondPage: SecondPage,
Account: Account,
About: About,
},
{
initialRouteName: 'HomeScreen',
headerMode: 'none',
navigationOptions: {
headerVisible: false,
},
},
);
export default createAppContainer(AppNavigator);
Here is the basic boilerplate for my second page:
const SecondPage: () => React$Node = () => {
return (
<>
<StatusBar barStyle="dark-content" />
<View style={styles.body}>
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>This is the Secondpage</Text>
</View>
</View>
</>
);
};
export default SecondPage;
You have three ways of doing this using navigationOptions:
1) Directly when you define your navigator:
createStackNavigator({
{
HomeScreen: {
screen : HomePage,
navigationOptions: {header : null}
},
SecondPage: SecondPage,
Account: Account,
About: About,
},
{ ... } //your navigationOptions
})
2) Directly inside the screen as said by Shashank Malviya. Only if you aren't using a functional component.
3) Return defaultNavigationOptions as a function and use navigation state:
createStackNavigator({
{ ... }, //your RouteConfigs
{
initialRouteName: 'HomeScreen',
headerMode: 'none',
defaultNavigationOptions : ({navigation}) => navigation.state.routeName === "HomePage" && {header: null}
},
})
Can be written on every component before render
static navigationOptions = ({ navigation }) => {
const { state } = navigation
return {
headerVisible: true // can be true or false
}
}

contentComponent not working React Native Navigation

As it says in the title, contentComponent not working. I cant quite figure out why it is not rendering the content in contentcomponent.
It shows the screens 1 2 and logout that I have in the drawer navigator instead of it. Any suggestions is appreciated?
Routes.js
import React from 'react';
import {
createAppContainer,
createSwitchNavigator,
createStackNavigator,
createBottomTabNavigator,
createDrawerNavigator
} from 'react-navigation';
import { SideNavigation } from './SideNavigation';
export const SignedOut = createStackNavigator(
{
Login: {
screen: LoginForm,
navigationOptions: {
header: null
}
},
Register: {
screen: RegisterForm
},
VerifyPhone: {
screen: PhoneVerify,
navigationOptions: {
headerLeft: null
}
}
},
{
initialRouteName: "Login"
}
);
export const Tabs = createBottomTabNavigator({
Main: {
screen: createStackNavigator({
Main: Main
}),
},
Events: {
screen: createStackNavigator({
Events: Events
}),
},
Shop: {
screen: createStackNavigator({
Shop: Shop
}),
},
Profile: {
screen: createStackNavigator({
Profile: Profile
}),
},
});
export const Stack = createStackNavigator(
{
Drawer: {
screen: SideNavigation,
navigationOptions: {
header: null,
},
},
DefaultScreen: {
screen: Main,
}
}
);
export const createRootNavigator = (loggedin) => {
return createAppContainer(createSwitchNavigator(
{
SignedIn: {
screen: Stack
},
SignedOut: {
screen: SignedOut
},
},
{
//initialRouteName: loggedin ? "SignedIn" : "SignedOut"
initialRouteName: "SignedIn"
}
));
};
SideNavigation.js
import { createDrawerNavigator, SafeAreaView } from 'react-navigation';
import { ScrollView } from 'react-native-gesture-handler';
import { Text, Dimensions } from 'react-native';
import { Stack } from "./Routes";
export const SideNavigation = createDrawerNavigator(
{
home: Stack
},
{
contentComponent: (props) => {
return <Drawer {...props} >
<ScrollView>
<SafeAreaView
forceInset={{top: 'always', horizontal: 'never'}}
>
<Text
onPress={() => {
props.navigation.navigate('Screen1');
props.navigation.closeDrawer();
}}
>
Testing Side 1
</Text>
<Text
onPress={() => {
props.navigation.navigate('Screen2');
props.navigation.closeDrawer();
}}
>
Testing side 2
</Text>
</SafeAreaView>
</ScrollView>
</Drawer>
},
}
);
Edit: Code has been updated. i am now getting an error that says 'home'must be a React component.
give your custom component as a separated component file, like this:
import Drawer from "./somewhere";
const Stack = createStackNavigator(
{
DefaultScreen: {
screen: Main,
}
}
);
const DrawerNavigator = createDrawerNavigator({
home: Stack <<<<this is an address of the stack navigator you should created above
}, {
contentComponent: (props) => {
return <Drawer {...props} /> <<<<Drawer
},
drawerPosition: 'right',
drawerType: 'slide',
drawerWidth: width * 0.8
}
)
more logical and cleaner... but don't forget to give drawer to your app container:
return createAppContainer(createSwitchNavigator(
{
SignedIn: {
screen: DrawerNavigato//not stack
},
SignedOut: {
screen: SignedOut
},
},
{
//initialRouteName: loggedin ? "SignedIn" : "SignedOut"
initialRouteName: "SignedIn"
}
));

Use the same custom header in all pages in React Native using AppContainer problems

I'm using react-native with react-navigation, and I'm hitting this issue with my screens not rendering correctly. I'm referencing this link to use the same disappearing header as a wrapper around my AppContainer:
https://medium.com/appandflow/react-native-scrollview-animated-header-10a18cb9469e
The first screen AuthLoadingScreen shows up fine, and then is supposed to navigate to the either of the stack's based on whether the user is verified. But for some reason, nothing shows up after the user is verified or not. I have a console.log upon entering both stacks and they both fire fine, just nothing gets rendered... any ideas are appreciated.
Here are some snippets of code that I have so far:
<- App.js ->
<View style={styles.fill}>
<Animated.ScrollView
style={styles.fill}
scrollEventThrottle={1}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
{ useNativeDriver: true },
)}
>
<-----My AppContainer----->
<AppContainer />
<------------------------->
</Animated.ScrollView>
<Animated.View
pointerEvents="none"
style={[
styles.header,
{ transform: [{ translateY: headerTranslate }] },
]}
>
<Animated.Image
style={[
styles.backgroundImage,
{
opacity: imageOpacity,
transform: [{ translateY: imageTranslate }],
},
]}
source={require('./assets/images/casino/casino-10.jpg')}
/>
</Animated.View>
</View>
<-My App Container consists of->:
import AuthLoadingScreen from '../screens/AuthLoadingScreen';
const AppStack = createStackNavigator({
Home: { screen: HomeScreen },
Settings: { screen: SettingsScreen },
OfferCategories: { screen: OfferCategoriesScreen },
});
const AuthStack = createStackNavigator({
Login: LoginScreen
});
export default createAppContainer({createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
})});
You can do it with two options: Warp in the AppContainer, or define the header section inside the top level of navigators:
lets start with the first option:
you can do
const AppContainerNavigator = createAppContainer({createSwitchNavigator({
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
})});
export default class AppContainer extends Component {
render() {
return (
<YOUR_HEADER_COMPONENT>
<AppContainerNavigator
ref={nav => {
this.navigator = nav;
}}
/>
</YOUR_HEADER_COMPONENT>
)
}
}
Or the other one is to pass the header component to your navigator while define it something like the following:
let MY_HEADER_COMPONENT = {
headerLeft: () => {
return null;
},
header: () => {
return <YOUR_HEADER_COMPONENT></YOUR_HEADER_COMPONENT>;
},
headerRight: () => {
return null;
}
};
export default createStackNavigator({
AuthLoading:{
screen: AuthLoadingScreen,
navigationOptions: ({
navigation
}) => (MY_HEADER_COMPONENT)},
App:{
screen: AppStack,
navigationOptions: ({
navigation
}) => (MY_HEADER_COMPONENT)},
....
})
Hopefully this answers your question

How to use both custom TabNavigator and StackNavigator in react-navigation

I'm having some issue with react-navigation.
My navigation routes :
StackNavigator: main app navigation
-- WelcomeScreen
-- GuideScreen
-- TabNavigator: this is CustomTabs
+ MyHomeScreen
+ MyNotificationsScreen
+ MySettingsScreen
-- OtherScreen.
I use createNavigator, createNavigationContainer to build my own Navigation. You can view live for custom tab here: https://snack.expo.io/rJnUK4nrZ
import React from 'react';
import {
Button,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import {
createNavigator,
createNavigationContainer,
TabRouter,
addNavigationHelpers,
} from 'react-navigation'; // 1.0.0-beta.11
const SampleText = ({ children }) => (
<Text style={styles.sampleText}>{children}</Text>
);
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => {
navigation.goBack(null);
}}
title="Go back"
/>
</ScrollView>
);
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen banner="Home Screen" navigation={navigation} />
);
const MyNotificationsScreen = ({ navigation }) => (
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const CustomTabBar = ({ navigation }) => {
const { routes } = navigation.state;
return (
<View style={styles.tabContainer}>
{routes.map(route => (
<TouchableOpacity
onPress={() => navigation.navigate(route.routeName)}
style={styles.tab}
key={route.routeName}
>
<Text>{route.routeName}</Text>
</TouchableOpacity>
))}
</View>
);
};
const CustomTabView = ({ router, navigation }) => {
const { routes, index } = navigation.state;
const ActiveScreen = router.getComponentForState(navigation.state);
return (
<View style={styles.container}>
<CustomTabBar navigation={navigation} />
<ActiveScreen
navigation={addNavigationHelpers({
...navigation,
state: routes[index],
})}
/>
</View>
);
};
const CustomTabRouter = TabRouter(
{
Home: {
screen: MyHomeScreen,
path: '',
},
Notifications: {
screen: MyNotificationsScreen,
path: 'notifications',
},
Settings: {
screen: MySettingsScreen,
path: 'settings',
},
},
{
// Change this to start on a different tab
initialRouteName: 'Home',
}
);
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabRouter)(CustomTabView)
);
const styles = StyleSheet.create({
container: {
marginTop: Platform.OS === 'ios' ? 20 : 0,
},
tabContainer: {
flexDirection: 'row',
height: 48,
},
tab: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
margin: 4,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 4,
},
sampleText: {
margin: 14,
},
});
export default CustomTabs;
In the App.js
import { connect } from "react-redux";
import { addNavigationHelpers, StackNavigator } from "react-navigation";
import Welcome from "#components/WelcomeScreen";
import Welcome from "#components/GuideScreen";
import Welcome from "#components/OtherScreen";
// import CustomTabs
export const AppNavigator = StackNavigator(
{
Welcome: { screen: WelcomeScreen },
Guide: { screen: GuideScreen },
Home: { screen: CustomTabs }, // I want to use CustomTabs TabNavigation here?
Other: { screen: OtherScreen },
},
{
initialRouteName: "Welcome",
headerMode: "none"
}
);
const Routes= ({ dispatch, nav }) => (
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav })} />
);
const mapStateToProps = state => ({
nav: state.nav
});
const mapDispatchToProps = dispatch => ({
dispatch
});
export default connect(mapStateToProps, mapDispatchToProps)(AppWithNavigationState);
How to add my custom TabNavigator to main StackNavigator?
What I'm doing wrong? The app integrated with redux, saga. If you have other example about custom stack and tab using react-navigation, please give me to reference.
Update Your Code:
import Tab1Screen from "#components/Tab1Screen"
import Tab2Screen from "#components/Tab2Screen"
export const AppNavigator = StackNavigator(
{
Welcome: { screen: WelcomeScreen },
Guide: { screen: GuideScreen },
Home: {
screen: TabNavigator(
{
Tab1Screen: {screen: Tab1Screen},
Tab2Screen: {screen: Tab2Screen}
},{
tabBarPosition: "bottom"
})
},
Other: { screen: OtherScreen },
},
{
initialRouteName: "Welcome",
headerMode: "none"
}
);
We can access TabNavigation inside of StackNavigtion
import files
import SampleHeadersScreen from "../Containers/SampleHeadersScreen";
import SampleHeaders1Screen from "../Containers/SampleHeaders1Screen";
import SampleHeaders3Screen from "../Containers/SampleHeaders3Screen";
import SampleHeaders8Screen from "../Containers/SampleHeaders8Screen";
import SampleHeaders9Screen from "../Containers/SampleHeaders9Screen";
const TabSegment = TabNavigator(
{
SegmentBarScreen: {
screen: SampleHeaders8Screen,
navigationOptions: {
title: "Album"
}
},
SegmentBarScreen1: {
screen: SampleHeaders9Screen,
navigationOptions: {
title: "Other"
}
}
})
const PrimaryNav = StackNavigator(
{
LaunchScreen: { screen: LaunchScreen },
SampleHeadersScreen: { screen: SampleHeadersScreen },
SampleHeaders1Screen: { screen: SampleHeaders1Screen },
SampleHeaders3Screen: { screen: SampleHeaders3Screen },
//TabNavigaion
SampleSegmentTab: { screen: TabSegment },
})
Give TabSegment in side the stack nativion.
It work well. I hope it will help you.

Nesting multiple navigators

I'm trying to integrate StackNavigator, TabNavigator and DrawerNavigator like this:
class TestApp extends Component {
render() {
return (
<MyApp />
);
}
}
const Tabs = TabNavigator({
Home1: { screen:Home1 },
Home2: { screen:Home2 },
},
{
tabBarPosition: 'bottom',
});
const Drawer = DrawerNavigator({
First:{
screen: DrawerScreen1
},
Second:{
screen: DrawerScreen2
}
},{
initialRouteName:'First',
drawerPosition: 'left'
});
const MyApp = StackNavigator({
Screen1: {screen:Screen1},
Screen2: {screen:Screen2},
Tabs: {
screen: Tabs
},
Drawer:{
screen: Drawer
}
}, {
initialRouteName: "Tabs"
});
AppRegistry.registerComponent('TestApp', () => TestApp);
Calling the drawer:
<Button
onPress={() => this.props.navigation.navigate('DrawerOpen')}
title="Show Drawer"
/>
It does not open the drawer. It opens the DrawerScreen1 as if I'm calling it from StackNavigator. It opens the component with a back button. I want to open the drawer.
DrawerScreen1:
export default class DrawerScreen1 extends Component{
static navigationOptions = {
drawerLabel: 'Home',
drawerIcon: ({ tintColor }) => (
<Image
source={require('./myImage.png')}
style={height:100, width: 100}
/>
),
};
render() {
return (
<Button
onPress={() => this.props.navigation.navigate('DrawerOpen')}
title="DrawerOpen"
/>
);
}
}
DrawerScreen2 is similar. What am I missing? Please help.