I'm trying to implement a custom navigation drawer using react-navigation and react-native-elements. I also want all my screens to have a header (that has a hamburger from which I can control the drawer). The problem I'm facing is that the screen includes the header, while the drawer includes the screen, yet the header needs the drawer for its click event. So how do I pass the drawer object to my hamburger (inside the header component)?
The header in the file UniversalHeader.js is as follows
export default class UniversalHeader extends React.Component {
render() {
return (
<Header
leftComponent={{ icon: 'menu', color: '#ea0', onPress: () => this.props.navigation.navigate('DrawerToggle'), size: 50}}
centerComponent={{ text: this.props.label, style: { color: '#fff' } }}
/>
);
}
}
In my home screen, I have
import UniversalHeader from './UniversalHeader'
export default class HomeScreen extends React.Component {
render() {
return (
<View>
<UniversalHeader />
<Text> cards go here </Text>
</View>
);
}
}
Then in the compiling script collating the screens, I have
import HomeScreen from './home';
class customNav extends React.Component {
render() {
return (
<View>
<Avatar rounded xlarge source={require('./images/logo.png')} />
<DrawerNavigatorItems navigation={{state: [{routeName: 'Home', key: '0'}]}}
items={['Home', 'Discussions', 'Podcasts', 'Sermons', 'Events']}
onItemPress={route => this.props.navigation.navigate(route)}
renderIcon={(routeIcon) => (
<Image source={{uri: `./images/${routeIcon}.png`}} style={[styles.icon, {/*tintColor: tintColor*/}]} />
)}
getLabel={route => route.toString()}
/>
</View>
)
}
}
const MyApp = DrawerNavigator({
Home: {
screen: HomeScreen
}
}, {drawerBackgroundColor: '#000', contentComponent: customNav});
The onPress method in Header leftComponent has no idea of who navigator is but I don't know how to get it across either from the screen 'home.js' or from the 'UniversalHeader.js'.
The navigation property is automatically added to every component passed as a screen to any react-navigation component. So by proactively passing this.props.navigation of homescreen component to universalHeader i.e
<UniversalHeader navigation={this.props.navigation} />
You'll be giving it the navigator that's aware of all your registered classes/screen (in this case, passed into DrawerNavigator)
Related
In react-native-paper if I am using TextInput with Icon like this it works fine.
<TextInput
label="Password"
right={<TextInput.Icon icon="eye" />}
/>
But if I export the Icon part to custom element it stop showing the Icon.
Am I doing something wrong here?
const MyCustomIcon = () => {
return <TextInput.Icon icon="eye" />;
};
const MyComponent = () => {
return (
<TextInput
label="Password"
right={<MyCustomIcon />}
/>
);
};
export default MyComponent;
Thanks.
I currently working in a React Native project using 'https://www.npmjs.com/package/react-native-raw-bottom-sheet' lib to handle bottom up panel.
I want to trigger bottom up panel everytime I click a button in parent component instead of clicking a button in child component, how do I can achieve this.
Bottom Up Panel child component:
const refRBSheet : any = useRef();
View style={styles.container}>
<View style={{width:200}}>
<Button title="OPEN BOTTOM SHEET" onPress={() => refRBSheet.current.open()} />
</View>
<RBSheet
ref={refRBSheet}
closeOnDragDown={true}
closeOnPressMask={true}
animationType='slide'
customStyles={{
wrapper: {
backgroundColor: "transparent"
},
draggableIcon: {
backgroundColor: "#999999"
},
container:{
backgroundColor:'#101010',
height:450,
}
}}
>
<MyComponent/>
</RBSheet>
</View>
My parent component:
<View>
<Button title="I want to click this button and trigger ButtomUpPanel/>
<BottomUpPanel/>
</View>
You need to use the useImperativeHandle hook in your parent
const refRBSheet : any = useRef();
const open = () => {
refRBSheet.current.open()
};
useImperativeHandle(refRBSheet, () => ({
open,
}));
Then pass the ref down to your components.
<View>
<Button onPress={()=>open()} title="I want to click this button and trigger ButtomUpPanel/>
<BottomUpPanel ref={refRBSheet} />
</View>
Remember useImperativeHandle should be used with forwardRef.
Actually after hours of researching I finally found a way for my own question:
To pass a ref to child component from parent component I use
forwardRef
By importing import
{forwardRef} from 'react';
Then in my case i just wrap my child component in forwardRef:
const DetailPanelBottomUp: FC<IDetailPanel> = forwardRef(
({imgUrl, title, contentUrl,value}, ref: any) => {
return (
<View style={styles.container}>
<RBSheet
ref={ref}
onClose={() => setSocialNet(false)}
closeOnDragDown={true}
closeOnPressMask={true}
animationType="slide"
customStyles={{
wrapper: {
backgroundColor: 'transparent',
},
draggableIcon: {
backgroundColor: '#999999',
},
container: {
backgroundColor: '#101010',
height: 450,
},
}}>
<MyComponent/>
</RBSheet>
</View>
And in my parent component I just declare
useRef
and its action then pass the ref to child component as a props
const refRBSheet : any = useRef();
const open = () => {
refRBSheet.current.open();
};
<View>
<DetailPanelBottomUp title={actionItem.title} contentUrl={actionItem.src} ref=
{refRBSheet} imgUrl={actionItem.src} />
<Button onPress={()=>open()} title="I want to click this button and trigger ButtomUpPanel/>
</View>
I'm currently using the material-bottom-tabs to implement navigation in my mobile app.
For some odd reason, it isn't displayed correctly but cut off at the bottom of my screen.
This happens regardless of whether I activate gesture control (so the Android built-in navigation bar disappears) or not.
If I add padding to the style of my Tabs.Navigator, then the Tab-Navigation-Bar is still cut off, now by a white padding.
I tried to wrap my Tab.Navigator inside a SafeAreaView (from react-native-safe-area-context) but if I do this, I just get a plain white screen back.
This is my code:
import React, { Component } from 'react';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import DrawerHome from './DrawerHome';
import Bookmarks from './Bookmarks';
const Tab = createMaterialBottomTabNavigator();
const Stack = createStackNavigator();
//const insets = useSafeArea();
class App extends Component {
constructor(props) {
super(props);
this.state = {
userToken: 1, // set to 'null' in production state
};
}
render() {
return this.userToken === null ? (
<Stack.Screen name="LogIn" component={LoginScreen} />
) : (
<SafeAreaProvider>
<NavigationContainer>
<SafeAreaView>
<Tab.Navigator style={{ paddingBottom: '10%' }}>
<Tab.Screen
name="Current Schedule"
component={CarouselPage}
options={{
tabBarLabel: 'Current\nSchedule',
tabBarIcon: <Ionicons name="ios-calendar" size={20} />,
}}
/>
<Tab.Screen name="Resources" component={ResourceScreen} />
<Tab.Screen
name="Schedule Selection"
component={ScheduleSelectionScreen}
/>
<Tab.Screen name="About" component={AboutScreen} />
</Tab.Navigator>
</SafeAreaView>
</NavigationContainer>
</SafeAreaProvider>
);
}
}
export default App;
A screenshot of the display issue
Try this:
// ...
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name="Current Schedule"
component={CarouselPage}
options={{
tabBarLabel: 'Schedule',
tabBarIcon: ({}) => (
<Ionicons name="ios-calendar" size={20} />
),
}}
/>
// ...
</Tab.Navigator>
</NavigationContainer>
// ...
The bar isn't cut off. The reason the text was cut off was, because you put a newline in the tabBarLabel text. I recommend to choose shorter words for your tab labels.
The documentation does not seem to provide an option to increase the padding for the label to allow for longer text.
I'm integrating DrawerNavigator of 'react-navigation' to my project as this document. But when I run the project it always get this error when click on the Button:
TypeError: undefined is not an object (evaluating
'this.props.navigation.navigate')
And when I swipe from left to right, nothing happen, no drawer open.
I've check this.props and it's always log empty {} in console.
I tried many solutions but it's still not working.
calculator.js
export default class Calculator extends Component {
constructor(props) {
super(props);
}
static navigationOptions = {
drawerLabel: 'Calculator',
drawerIcon: ({ tintColor }) => (
<Image
source={require('./../../res/images/icon_calculator.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
render() {
return (
<View style={styles.container}>
<View>
<Text style={styles.title}>Tip Calculator</Text>
</View>
<Button
onPress={() => this.props.navigation.navigate("SettingsScreen")}
title="Go to settings"
/>
</View>
);
}
}
module.exports = Calculator;
settings.js
export default class Settings extends Component {
constructor(props) {
super(props);
}
static navigationOptions = {
drawerLabel: 'Settings',
drawerIcon: ({ tintColor }) => (
<Image
source={require('./../../res/images/icon_settings.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
render() {
return (
<View style={styles.container}>
<Text>Settings</Text>
<Button
onPress={() => this.props.navigation.goBack()}
title="Go back home"
/>
</View>
);
}
}
module.exports = Settings;
navigation.js
import {
DrawerNavigator
} from 'react-navigation';
import Calculator from './../components/calculator/calculator.js';
import Settings from './../components/settings/settings.js';
const RootDrawer = DrawerNavigator({
CalculatorScreen: {
path: '/',
screen: Calculator
},
SettingsScreen: {
path: '/sent',
screen: Settings
}
}, {
initialRouteName: 'CalculatorScreen',
drawerPosition: 'left'
});
export default RootDrawer;
App.js
export default class App extends Component<{}> {
render() {
return (
<Calculator/>
);
}
}
index.js
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('rn_tip_calculator', () => App);
Do I have to use StackNavigator with DrawerNavigator, or am I missed something in config?
Full source code, it's only a simple example project, please have a look: https://github.com/HCMUS-IceTeaViet-SE/rn_tip_calculator
Any help will be appreciated. Thanks!
You can use dispatch api https://reactnavigation.org/docs/navigators/navigation-actions
1) import navigation actions
import { NavigationActions } from 'react-navigation'
2) dispatch navigation action:
const navigateAction = NavigationActions.navigate({
routeName: 'SettingsScreen',
params: {},
})
this.props.navigation.dispatch(navigateAction)
I'm pretty new to React-Native but sometimes this also happens to me.
I'm using Redux and stack navigator.... but here is my working example...
import { StackNavigator } from 'react-navigation'
import { Animated, Easing } from 'react-native'
import LoginScreen from '../Containers/LoginScreen'
import LaunchScreen from '../Containers/LaunchScreen'
import HomeScreen from '../Containers/HomeScreen'
import SignUpScreen from '../Containers/SignUpScreen'
import SettingsScreen from '../Containers/SettingsScreen'
import VehicleCreateScreen from '../Containers/VehicleCreateScreen'
import styles from './Styles/NavigationStyles'
// Manifest of possible screens
const PrimaryNav = StackNavigator({
LoginScreen: { screen: LoginScreen },
LaunchScreen: { screen: LaunchScreen },
HomeScreen: { screen: HomeScreen },
SignUpScreen: { screen: SignUpScreen },
SettingsScreen: { screen: SettingsScreen },
VehicleCreateScreen: { screen: VehicleCreateScreen }
}, {
// Default config for all screens
headerMode: 'none',
initialRouteName: 'LaunchScreen',
navigationOptions: {
headerStyle: styles.header
},
transitionSpec: {
duration: 0,
timing: Animated.timing,
easing: Easing.step0,
},
},
)
export default PrimaryNav
And then from a component not connected to REDUX
import React, { Component } from 'react';
import { Container, Content, List, ListItem, Icon, Text, Button, Left, Right, Badge } from 'native-base';
import { Image } from 'react-native'
import styles from './Styles/SideBarStyle';
// import backgroundImage from '../Images/vw.jpg'
const backgroundImage = require("../Images/vw.jpg");
const drawerImage = require("../Images/dirtyHandsDark.jpg");
export default class SideBar extends Component {
constructor(props) {
super(props);
}
render() {
// *********** HERE WE DECLARE AN ARRAY TO RENDER LISTS FROM. THIS COULD ALSO BE LIST OF BIKES FROM STORE.. ***********
const datas = [
{
name: "Home",
route: "HomeScreen",
icon: "settings",
bg: "#C5F442",
},
{
name: "Repair",
route: "HomeScreen",
icon: "settings",
bg: "#C5F442",
},
{
name: "My Profile",
route: "SettingsScreen",
icon: "settings",
bg: "#C5F442",
},
];
return (
<Container>
<Content bounces={false} style={{ flex: 1, backgroundColor: "#fff", top: -1 }}>
<Image source={backgroundImage} style={styles.drawerCover}>
<Image square style={styles.drawerImage} source={drawerImage} />
</Image>
<List
dataArray={datas}
renderRow={data =>
// *********** CREATE NEW LIST ITEM ON CLICK NAVIGATE TO APPROPRIATE LISTITEM.SCREEN ***********
<ListItem button noBorder onPress={() => this.props.navigation.navigate(data.route)}>
<Left>
<Icon active name={data.icon} style={{ color: "#777", fontSize: 26, width: 30 }} />
<Text style={styles.text}>
{data.name}
</Text>
</Left>
{data.types &&
<Right style={{ flex: 1 }}>
<Badge
style={{
borderRadius: 3,
height: 25,
width: 72,
backgroundColor: data.bg,
}}
>
<Text style={styles.badgeText}>{`${data.types} Types`}</Text>
</Badge>
</Right>}
</ListItem>}
/>
</Content>
</Container>
);
}
}
You can see I reference this.props.navigation.navigate no problem.
Heres my repo for reference.
https://github.com/GavinThomas1192/motoMechanicMeeKanic/tree/master/App
This happen because I don't "connect" my DrawerNavigator to my app.
There is 2 ways to achieve this:
First way: register DrawerNavigator as app root component. In index.js change:
from AppRegistry.registerComponent('rn_tip_calculator', () => App);
to AppRegistry.registerComponent('rn_tip_calculator', () => RootDrawer);
So you could delete App.js because it's useless now.
Second way: Keep register App component (in App.js) as app root component. Then put inside App component to "connect" DrawerNavigator to the app.
export default class App extends Component<{}> {
render() {
return (
<RootDrawer/>
);
}
}
The document say nothing about how to connect the navigator to the app, nothing about register component or put navigator inside root component. This drive newbie like me mad!
I need to use drawer from native base into react native app for both android ios et android.
Here is the link for native base http://nativebase.io/docs/v2.0.0/components#drawer and below you'll find my code :
import { Container, Header, Title, Content, Button, Icon, Left, Body, Text } from 'native-base';
import { Drawer } from 'native-base';
import SideBar from '../components/SideBar';
class App extends Component {
closeDrawer = () => {
this._drawer._root.close();
}
openDrawer = () => {
alert('open');
this._drawer._root.open();
}
render() {
return (
<Container>
<Header style={{ backgroundColor: '#C0C0C0' }}>
<Left>
<Button transparent onPress={this.openDrawer.bind(this)}>
<Icon style={style.icon} name='menu' />
</Button>
</Left>
<Body style={style.body}>
<Title style={{ color: '#FFF'}}> title </Title>
</Body>
</Header>
<Content>
<Drawer
ref={(ref) => { this._drawer = ref; }}
content={<SideBar />} >
</Drawer>
</Content>
</Container>
);
}
the alert in the method open drawer is working fine, so i know it's not a problem in the button.
I believe you want to wrap everything in the drawer, like so
render() {
return (
<Drawer
ref={(ref) => { this._drawer = ref; }}
content={<SideBar />} >
<Container>
<Header style={{ backgroundColor: '#C0C0C0' }}>
<Left>
<Button transparent onPress={this.openDrawer.bind(this)}>
<Icon style={style.icon} name='menu' />
</Button>
</Left>
<Body style={style.body}>
<Title style={{ color: '#FFF'}}> title </Title>
</Body>
</Header>
<Content>
// Your other content here
</Content>
</Container>
</Drawer>
);
}
Also, on your self-made sidebar component - make sure it has a backgroundColor. Set it to something like #F0F0F0 otherwise it ends up looking mighty strange.
I am writing answer for anyone who is new to developing apps using react native, for this answer the important thing which I will be using is react-navigation.
First is the app.js where we declare the drawer and the other screens which includes a login screen without the drawer menu. The authLoading Screen is used to navigate the users to login or home screen based on the fact whether they are authenticated or not.
App.js
const HomeScreenRouter = createDrawerNavigator(
{
Home: { screen: HomeScreen }
},
{
contentComponent: props => <SideBar {...props} />
}
);
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createAppContainer(createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: HomeScreenRouter,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
));
with the contentComponent we get the sliding menu in our home screen the Sidebar is a simple component which can have things as desired. Now for the homescreen we will have a button which will also allow users to open the menu from anywhere.
class HomeScreen extends React.Component {
render() {
return (
<Container>
<Header>
<Left>
<Button
transparent
onPress={() => this.props.navigation.openDrawer()}>
<Icon name="menu" />
</Button>
</Left>
<Body>
<Title>Be-in</Title>
</Body>
<Right />
</Header>
<Content>
</Content>
</Container>
);
}
}
export default HomeScreen
versions used in this example are
"react": "16.6.1",
"react-native": "0.57.7",
"react-navigation": "^3.0.8",
I hope it will be helpful to anyone who is intending to implement a drawer and also enable navigation.