React Native : Include both header and footer components within Navigator - react-native

I have created an app which uses the Navigator component, I'm wondering is there a way I can implement a header and footer component outside of the scene?
A screenshot of what I have currently:
http://i.stack.imgur.com/XHDKC.png
This first attempt was accomplished by making the header and footer a single component with absolute styles.
//index.js
<Navigator initialRoute={{id: 'home', title: window.title}}
renderScene={renderScene}
navigationBar={<DefaultHeader toggleSideMenu={this.toggleSideMenu}
route={route.id} />}/>
//DefaultHeader.js
<View style={styles.navContainer}>
<View style={styles.header}>
</View>
<View style={styles.footer} shouldUpdate={false}>
</View>
</View>
Although appeared to work, I was unable to click around anything within the scene due to the render order in React's Navigator component.

I decided to re-think my approach and fully separated navigation bars from the Navigator component. This relies on you passing down a routing function and any other route info.
routeTo: function (route) {
if (route.to == "back") {
this.refs.navigator.pop();
} else {
this.refs.navigator.push(route);
}
},
canGoBack: function () {
return this.refs.navigator && this.refs.navigator.getCurrentRoutes().length > 1
},
getDefaultRoute: function () {
return {id: 'home', title: window.title};
},
getCurrentRoute: function () {
if (this.refs.navigator) {
return _.last(this.props.navigator.getCurrentRoutes());
}
return this.getDefaultRoute();
},
render() {
return (
<View style={styles.container}>
<DefaultHeader routeTo={this.routeTo} route={this.getCurrentRoute()}
toggleSideMenu={this.toggleSideMenu}/>
<Navigator
ref="navigator"
initialRoute={this.getDefaultRoute()}
renderScene={renderScene}
/>
<DefaultFooter routeTo={this.routeTo} route={this.getCurrentRoute()}/>
</View>
)
}

Although it is pretty "hacky" - why don't you add a third view (expanding fully) between the header and footer and set onStartShouldSetResponder and onMoveShouldSetResponder to return false for both: the middle view and the navContainer view). See https://facebook.github.io/react-native/docs/gesture-responder-system.html. I am not sure if it will work but it might be worth trying.
The best way, however, would be to modify the Navigator component and add footer props and displaying there. It's pure javascript, so it should be fairly easy to do.

I am using RN 0.36 and I was able to workaround this by using the navigator height to margin the footer:
<View style={{flex: 1}}>
<ScrollView>
...
</ScrollView>
<View style={{
height: 40,
borderTopWidth: 1,
borderTopColor: colors.grey,
flex: 0,
marginBottom: Navigator.NavigationBar.Styles.General.TotalNavHeight
}}>
<Text>Footer</Text>
</View>
</View>
where my index files (ie index.ios.js) looks like
<NavigatorIOS
style={{flex: 1}}
initialRoute={{
title: ' ',
component: Main
}}
...
Check NavigatorIOS and Navigator

Related

Create a reusable React Native Modal Component

I'm going back to basics with React Native, as I feel overwhelmed. I have been looking for an implementation of a reusable modal component. I'm looking for examples of a reusable Modal component in RN? Thanks in advance
You can find many examples of this on StackOverflow. Still, if you need example I can help you with one example. You have mentioned modal component in your question, right?
Your component will look like this with props. let the name be ModalComponent for this file.
render() {
const { isVisible, message, textValue } = this.props;
return (
<Modal
animationType="slide"
transparent={false}
isVisible={isVisible}
backdropColor={"white"}
style={{ margin: 0 }}
onModalHide={() => {}}>
<View>
<Text>textValue</Text>
<Text>message</Text>
</View>
</Modal>
);
}
so now in your js file you need to import this modalComponent and after that, you need to write as
<ModalComponent
isVisible={true}
textValue={'hi there'}
message={'trying to make a basic component modal'}/>
Hope this will help for you
EDIT:
Create seperate components that you want to render inside modal. for Ex: component1.js, component2.js, component3.js with props
component1.js:
render(){
const { textVal, message } = this.props
return (
<View>
<Text>{textVal}</Text>
<Text>{message}</Text>
</View>
)
}
now in ModalComponent
render() {
const { first, second, third, isVisible, component1Text, component1Message } = this.props;
<Modal
animationType="slide"
transparent={false}
isVisible={isVisible}
backdropColor={"white"}
style={{ margin: 0 }}
onModalHide={() => {}}>
<View>
{first && <component1
textValue= component1Text
message= component1Message />}
{second && <Component2 />}
{third && <Component2 />}
</View>
</Modal>
In this way, you can achieve it within the single modal.
You will make a component like this giving the parent component all the liberty to change it through props.
render() {
const { isVisible, message, textValue, animationType, backDropColor, style, onModalHide, children } = this.props;
return (
<Modal
animationType= {animationType || 'slide'}
transparent={transparent || false}
isVisible={isVisible || false}
backdropColor={backdropColor || "white"}
style={[modalStyle, style]}
onModalHide={onModalHide}>
{children}
</Modal>
);
}
Then in your parent component, you need to import this component like this:
import ModalComponent from '../ModalComponent'; //path to your component
<ModalComponent isVisible={true}>
<View>
//any view you want to be rendered in the modal
</View>
</ModalComponent>
I had a lot of troubles using react-native modal, sometimes i started the app and could not close it even when i set the isVisible prop to false, it is even worst on IOs, i did a research and these packages are not being maintained properly.
You will save a lot of time by using a top-level navigator like is recommended in the modal docs: https://facebook.github.io/react-native/docs/modal.
I tried https://github.com/react-native-community/react-native-modal but had the same problems because its an extension of the original react-native modal.
I suggest you to use the react-navigation modal as described here: https://reactnavigation.org/docs/en/modal.html#docsNav
You can refer the following code to write Modal component once and use multiple times.
Write once:
import React, { Component } from 'react';
import { View, Text, Button, Modal, ScrollView, } from 'react-native';
export class MyOwnModal extends Component {
constructor(props) {
super(props);
this.state = {
}
render() {
return(
<Modal
key={this.props.modalKey}
transparent={this.props.istransparent !== undefined ? true : false}
visible={this.props.visible}
onRequestClose={this.props.onRequestClose}>
<View style={{
//your styles for modal here. Example:
marginHorizontal: width(10), marginVertical: '30%',
height: '40%', borderColor: 'rgba(0,0,0,0.38)', padding: 5,
alignItems: 'center',
backgroundColor: '#fff', elevation: 5, shadowRadius: 20, shadowOffset: { width: 3, height: 3 }
}}>
<ScrollView contentContainerStyle={{ flex: 1 }}>
{this.props.children}
</ScrollView>
</View>
</Modal>
);
}
}
Now,
You can call your Modal like following example: (By doing this, you avoid re-writing the Modal and its outer styles everytime!)
Example
<MyOwnModal modalKey={"01"} visible={true} onRequestClose={() =>
this.anyFunction()} istransparent = {true}>
<View>
// create your own view here!
</View>
</MyOwnModal>
Note: If you are in using different files don't forget to import , and also you can pass the styles as props.
(You can create/customise props too based on your requirement)
Hope this saves your time.
Happy coding!
I am a contributor of react-native-use-modal.
This is an example of creating a reusable modal in a general way and using react-native-use-modal: https://github.com/zeallat/creating-reusable-react-native-alert-modal-examples
With react-native-use-modal, you can make reusable modal more easily.
This is a comparison article with the general method: https://zeallat94.medium.com/creating-a-reusable-reactnative-alert-modal-db5cbe7e5c2b

Percentage does not work with FlatList render item when horizontal is true

I would like to use the screen's width on the render item of the horizontal FlatList. However, it does not work as expected. When the horizontal is false, the percentage value works. But when the horizontal is true, the percentage value does not work.
class App extends React.Component {
_renderItem = ({ item }) => {
return (
<View
style={{
width: '100%',
height: 100,
}}>
<Text>{item.key}</Text>
</View>
);
};
render() {
return (
<View style={styles.container}>
<FlatList
data={[{ key: 1 }, { key: 2 }, { key: 3 }]}
renderItem={this._renderItem}
horizontal={true}
/>
</View>
);
}
}
Snack link when the FlatList is horizontal
Snack link when the FlatList is NOT horizontal
I think I remember someone mentionning something like that. Using Dimensions works here. See here: https://snack.expo.io/H1-wnC5HM
I rather solve it with flex or percentage but well.

How to add section divider in navigation drawer using react navigation

Suppose I have five items in drawer navigation. I want to add separator after three items. How to add this using react-navigation.
As mentioned vonovak, you could achieve this by using contentComponent which allows complete customization of drawer. For this you will need to create custom component which will override default drawer. Code sample:
Navigation Drawer
const NavigationDrawer = DrawerNavigator({
screen1: { screen: Screen1 },
screen2: { screen: Screen2 },
screen3: { screen: Screen3 },
}, {
contentComponent: SideMenu
})
Custom component which overrides default drawer (DrawerContainer)
class SideMenu extends React.Component {
render() {
return (
<View style={styles.container}>
<ScrollView>
<Text
onPress={() => this.props.navigation.navigate('Screen1')}
style={styles.item}>
Page1
</Text>
// 'separator' line
<View
style={{
borderBottomColor: 'black',
borderBottomWidth: 1
}}/>
<Text
onPress={() => this.props.navigation.navigate('Screen2')}
style={styles.item}>
Page2
</Text>
<Text
onPress={() => this.props.navigation.navigate('Screen3')}
style={styles.item}>
Page3
</Text>
</ScrollView>
</View>
);
}
}
You'll need to use the contentComponent prop to make custom changes. Check out the docs
If you are using DrawerItem component you can use itemStyle prop to add style as follows
const props = {
itemStyle: {
borderBottomWidth: 1,
borderColor: '#E2E4E8',
}
}
<DrawerItems {...props} />
You can also modify the style of container containing all items with
itemsContainerStyle prop.
Check official docs here.
3.x: https://reactnavigation.org/docs/3.x/drawer-navigator#contentoptions-for-draweritems
4.x: https://reactnavigation.org/docs/4.x/drawer-navigator#contentoptions-for-draweritems
5.x: DrawerContent is default drawer Item from v.5
You can pass props to it using drawerContentOptions object in Drawer.Navigator component.
https://reactnavigation.org/docs/5.x/drawer-navigator#drawercontentoptions
You can also pass custom drawer component using drawerContent prop.
https://reactnavigation.org/docs/5.x/drawer-navigator#drawercontent
6.x: Custom component can be added using drawerContent prop
https://reactnavigation.org/docs/drawer-navigator#drawercontent
Style to drawerItem is changed to
https://reactnavigation.org/docs/drawer-navigator#draweritemstyle
Just add this code:
const Seperator = () => <View style={styles.separator} />;
at the top of your code that you want to have the section divider/separator on. If it's a navigation drawer, menu, categories or etc.
Add this styling prop:
separator: {
marginVertical: 8,
borderBottomColor: "#737373",
borderBottomWidth: StyleSheet.hairlineWidth
}
Add section divider/separator between each section, menu, category block of code that you want to separate them like this:
//Block of code of first section/menu/category starts from here
<Icon.Button
name="th-large"
raised={true}
backgroundColor="#ffa500"
size={30}
onPress={() => {
Linking.openURL("https://www.buymeacoffee.com/splendor");
}}
>
<Text style={{ fontSize: 15 }}>
Herat: The "Academy" of Prince Bay Sunghur (1420-1433)
</Text>
</Icon.Button>
**<Seperator />**
//Block of code of first section/menu/category ends here
//Block of code of second section/menu/category starts from here
<Icon.Button
name="th-large"
raised={true}
backgroundColor="#ffa500"
size={30}
onPress={() => {
Linking.openURL("https://www.buymeacoffee.com/splendor");
}}
>
<Text style={{ fontSize: 15 }}>
Venice, Istanbul, and Herat (15th Century)
</Text>
</Icon.Button>
**<Seperator />**
//Block of code of second section/menu/category ends here
//Block of code of third section/menu/category starts from here
<Icon.Button
name="th-large"
raised={true}
backgroundColor="#ffa500"
size={30}
onPress={() => {
Linking.openURL("https://www.buymeacoffee.com/splendor");
}}
>
<Text style={{ fontSize: 15 }}>
The Age of Bihzad of Herat (1465-1535)
</Text>
</Icon.Button>
**<Seperator />**
//Block of code of thirds section/menu/category ends here

React-native-popup-menu on react-navigation header

I'm using redux with react-navigation and would like to show the popup when the user clicks on the button on the react-navigation header-right button.
I wrapped the context menu at the root of my apps, as below
return (
<Provider store={store}>
<MenuContext style={{ flex: 1 }}>
<AppWithNavigationState />
</MenuContext>
</Provider>
)
in one of my screen, I have
static navigationOptions = {
headerTitle: 'News',
headerRight: (
<TouchableOpacity style={{ paddingLeft:15, paddingRight:15 }}>
<Icon name="more-vert" size={30} color="black" />
</TouchableOpacity>
),
}
When the user clicks on the right button, it should be like this
The menu items are dynamic, I will have to pull the data from one of my API and start rendering the menu data.
I've read through online it can be achieved using the context method, but I'm not sure how to implement it in my structure.
Could anyone advise me on this?
And is it possible to render it with my local variable?
The most custom way is to use Modal, when click the right button, called this.refs.modalRef.showModal(), which in your current page:
<View>
<PopupModal ref="modalRef" />
</View>
The PopupModal like this:
export default class PopupModal extends Component {
state = {
show: false,
}
showModal() {
this.setState({show: true});
}
closeModal = () => {
this.setState({show: false});
}
return (
<Modal
transparent
visible={this.state.show}
onRequestClose={this.closeModal}
>
<TouchableWithoutFeedback onPress={this.closeModal}>
<View style={{
width: '100%',
height: '100%',
opacity: 0.5,
backgroundColor: 'gray',
}} />
</TouchableWithoutFeedback>
<View></View> // your designed view, mostly position: 'absolute'
</Modal>
);
}
You can also pass some data to PopupModal by this.refs.modalRef.showModal(data), and in PopupModal:
showModal = (data) => {
this.setState({ data, show: true });
}
https://www.npmjs.com/package/react-native-material-menu
It works to me
headerRight:<View style={{marginRight:10}}>
<Menu
ref={this.setMenuRef}
button={<Text onPress={this.showMenu}><Icon style={{color:screenProps.headerTitleStyle.color,fontSize:25,marginRight:5}} name="md-more"/></Text>}
>
<MenuItem onPress={this.hideMenu}>Rate Us</MenuItem>
<MenuItem onPress={this.hideMenu}>Share App</MenuItem>
<MenuItem onPress={this.hideMenu}>Settings</MenuItem>
</Menu>
</View>,

How to set auto height for View with Navigator inside?

I use the default components View and Navigator. Wrapping the Navigator inside a View, I want it to automatically get the height of the content
(Navigator), but if I don't set an explicit height for the View component - the Navigator does not show, as if it had position: absolute.
If I add a Text component inside a View - the View automatically gets the height from the child Text component. But if I set 'height: auto' for View - this property doesn't work.
render() {
return (
<View style={{height: 'auto', backgroundColor: 'powderblue'}}>
<Navigator
initialRoute={{ title: 'My Initial Scene', index: 0 }}
renderScene={(route, navigator) => {
return (
<MyScene title={route.title} />
)
}}
/>
</View>
)
}
style={{height:'auto'}} will not work. You must use an integer or use flex. Here you can see an example of the options.
https://facebook.github.io/react-native/docs/height-and-width.html
Your code will look like this:
render() {
return (
<View style={{flex:1, backgroundColor: 'powderblue'}}>
<Navigator
style={{flex:1}}
initialRoute={{ title: 'My Initial Scene', index: 0 }}
renderScene={(route, navigator) => {
return (
<MyScene
title={route.title}
/>
)
}
}
/>
</View>
)
}