How to open/close Drawer from another component in React-Native - reactjs

I have a mobile app which has components base structure. For showing Drawer globally i've implemented it in my Home.js like code below, but my drawer toggler must be in my Header component that inside my Container component.
How it possible to send instance of main drawer to Header component and control opening/closing status?
Home.js code:
class Home extends Component {
render() {
const drawerContent = <DrawerComp />;
return (
<Drawer
ref={comp => {
this.drawer = comp;
}}
drawerWidth={250}
drawerContent={drawerContent}
type={Drawer.types.Overlay}
drawerPosition={Drawer.positions.Right}
maskAlpha={0.05}
onDrawerOpen={() => {
console.log("Drawer is opened !");
}}
onDrawerClose={() => {
console.log("Drawer is closed !");
this.props.dispatch(displayDrawer());
}}
>
<Container>
<StatusBar barStyle="light-content" />
<Header drawer={this.drawer} />
<TouchableHighlight
underlayColor="#118d95"
onPress={this.handleDisplayDrawer}
>
<Text>Open Right Drawer</Text>
</TouchableHighlight>
</Container>
</Drawer>
);
}
}
Header component:
class Header extends Component {
handleDisplayDrawer = () => {
//
};
render() {
return (
<View style={styles.container}>
<TouchableHighlight
onPress={this.handleDisplayDrawer}
style={styles.colSmall}
underlayColor="#ffffff"
>
<Image
style={styles.icon}
resizeMode="contain"
source={require("./images/icon-menu.png")}
/>
</TouchableHighlight>
</View>
);
}
}

Pass function to Header component which calls that function when you want.
class Home extends Component {
toggleDrawer = () => {
this.drawer.toggle(); // edit this according to drawer library
}
render() {
<Header onMenuPress={this.toggleDrawer} />
}
}
class Header extends Component {
render() {
<TouchableHighlight onPress={this.props.onMenuPress}>
<Image />
</TouchableHighlight>
}
}

You can write a setDrawerState function in Drawer.js. that actually closes the drawer. Something like
setDrawerState(isOpen) => {
this.setState({
isOpen,
})
}
In Your home.js , you already have ref to drawer component .
you can write following function.
close = () => {
this.drawer.setDrawerState(false)
}
open = () => {
this.drawer.setDrawerState(true)
}
<Header
close={this.close}
open={this.open}
/>

I struggled alot to find a solution so, If you are using custom header component then my suggestion is to render customer header in each screen and toggle it using:-
navigation.openDrawer();

Related

React-native navigation // move specific Drawer menu to title bar right button and hide the bottom menu which is linked with moved button

I'm using React-Navigation in React-native, and I'm a newbie to this, so I'm facing a number of difficult issues.
I am designing navigations with a mixture of Stack, Bottom and Drawer navigation.
My app has Intro Screen, Dashboard Screen, Profile Screen, Contact Screen.
Example Image
I thought almost successful, but I can't solve the problems below.
I want to move the "dashboard" menu(1) in Drawer to drawer title bar right button(2).
And I want to get rid of the duplicate "Dashoboard" menu(3) at the bottom.
my code..
// App.js
import React from 'react';
import Main from './components/Main';
class App extends React.Component {
render() {
return (
<Main />
);
}
}
// components/Main.js
import Intro from './__intro';
import DrawerNavigator from '../navigation/DrawerNavi';
const Stack = createStackNavigator();
class Main extends Component {
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name='Intro'
component={Intro}
options={
{headerShown: false}
}
/>
<Stack.Screen
name='Index'
component={DrawerNavigator}
options={
{headerShown: false}
}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
}
export default Main;
// components/__intro.js
class Intro extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.container_child_1}>
<TouchableOpacity
activeOpacity={0.8}
style={styles.button}
onPress={
() => {
this.props.navigation.navigate('Index')
}
}
>
<Text style={styles.text}>Go to Dashboard</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
// navigation/DrawerNavi.js
import TabNavigator from "./TabNavigator";
import IndexTap from "../components/Index";
import DashBord from "../components/DashBoard";
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="dashboard from drawerNavi"
component={IndexTap}
/>
<Drawer.Screen name="Contact" component={Contact} />
</Drawer.Navigator>
);
}
export default DrawerNavigator;
// components/Index.js
import DashBoard from './DashBoard';
import Profile from './Profile';
const Tap = createBottomTabNavigator();
class IndexTap extends Component {
render() {
return (
<Tap.Navigator>
<Tap.Screen
name="DashBoard"
component={DashBoard}
options={{
headerShown: false,
),
}}
// initialParams={{ title: 'DashBoard' }}
/>
<Tap.Screen
name="Profile"
component={Profile}
options={{
headerShown: false
// headerTitleAlign: 'center'
}}
// initialParams={{ title: 'Profile' }}
/>
</Tap.Navigator>
)
}
}
// components/Contact.js
const Contact = () => {
return (
<View style={styles.center}>
<Text>This is the contact screen</Text>
</View>
);
};
// components/Dashboard.js
class DashBoard extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.container_child_1}>
<Text>DashBoard Page</Text>
<TouchableOpacity
activeOpacity={0.8}
style={styles.button}
onPress={
() => {
this.props.navigation.navigate('Intro')
}
}
>
<Text style={styles.text}>Go to the Intro</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
// components/Profile.js
class Profile extends Component {
render() {
return (
<View style={styles.container}>
<View style={styles.container_child_1}>
<TouchableOpacity
activeOpacity={0.8}
style={styles.button}
onPress={
() => {
this.props.navigation.navigate('DashBoard')
}
}
>
<Text style={styles.text}>DashBoard</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
how can I do?
Do I need to redesign?

Is there a better or more elegant way to conditionally render different components in React Native?

I'm new to React Native and I'm trying to create an app with a custom navbar, that jumps between pages when you click links.
Here is an abbreviated version of my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dicePage: true,
itemsPage: false
}
}
dicePress = () => {
this.setState({
dicePage: true,
itemsPage: false
})
}
itemsPress = () => {
this.setState({
dicePage: false,
itemsPage: true
})
}
render() {
return (
<View style={styles.container}>
<ImageBackground source={backgroundTile} style={styles.bgImage}>
<View style={styles.content}>
<View style={styles.logoContainer}>
<Image source={require('./assets/images/Logo.png')} style={styles.logo} />
</View>
{this.state.dicePage && <DicePage />}
{this.state.itemsPage && <ItemsPage />}
<NavBar value='Dice' dicePress={this.dicePress} itemsPress={this.itemsPress} />
</View>
</ImageBackground>
</View>
);
}
}
export default App;
and
class NavBar extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.bottomNav}>
<TouchableOpacity onPress={this.props.dicePress}>
<Text key='dice' style={styles.nav}>Dice</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.props.itemsPress}>
<Text key='items' style={styles.nav}>Items</Text>
</TouchableOpacity>
</View>
)
}
}
export default NavBar;
So this works when I test the app - and I'm going to have 4 or 5 pages, so want more options than a ternary operator etc. - but I think there's probably a better or more elegant way to do this.
I've tried React Navigator and other things but am really looking for the absolute simplest way to achieve this (hopefully with DRY coding, as the repetition feels wrong to me). Should I be mapping links and functions for this? I don't think I've ever mapped functions before and realise that there probably should be a single adaptable function that would work for all.
Thanks for any advice!
First Approach
You could make a pages object where each key is an identifier for a page and where each corresponding value is the component that represents a page:
const pages = {
dicePage: DicePage,
itemsPage: ItemsPage
};
This way we can decide what should be rendered based on the key only, and all navigation can links can share the same onPress handler.
App.js
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentPage: "dicePage" // Default page
};
}
pagePress = (pageName) => {
this.setState({ currentPage: pageName });
};
render() {
const Page = pages[this.state.currentPage]; // What page should be rendered (based on the `currentPage` key)
return (
<View>
<View>
<Page />
<NavBar value="Dice" pagePress={this.pagePress} />
</View>
</View>
);
}
}
Navbar.js
class NavBar extends Component {
render() {
return (
<View>
<TouchableOpacity onPress={() => this.props.pagePress("dicePage")}>
<Text key="dice">Dice</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.props.pagePress("itemsPage")}>
<Text key="items">Items</Text>
</TouchableOpacity>
</View>
);
}
}
So if you want to expand this with more pages you only need to add key value pairs to the pages object and links to your Navbar.
I've removed some things like styling and images to simplify the example.
Second Approach
const pages = [
{ key: "dicePage", page: DicePage, pageLinkName: "Dice" },
{ key: "itemsPage", page: ItemsPage, pageLinkName: "Items" },
];
class NavBar extends Component {
render() {
return (
<View>
{pages.map((page) => {
return (
<TouchableOpacity
onPress={() => this.props.pagePress(page.key)}
key={page.key}
>
<Text>{page.pageLinkName}</Text>
</TouchableOpacity>
);
})}
</View>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentPage: "dicePage", // Default page
};
}
pagePress = (pageName) => {
this.setState({ currentPage: pageName });
};
render() {
const Page = pages.filter((page) => page.key === this.state.currentPage)[0]
.page;
return (
<View>
<View>
<Page />
<NavBar value="Dice" pagePress={this.pagePress} />
</View>
</View>
);
}
}
Largely the same approach, but structure of pages has changed where you now also don't need to manually add new links, but you only need to add new objects to pages, which is now an array.

How to navigate between screens with StackNavigator if I have different js files for each screen?

fairly new to React Native. I have read the stack navigator docs and still don't get how to do this.
So I have a bottom tab navigator, and I want to implement a stack navigator in one of the tabs, so I can navigate from the main tab screen to the second screen, but from the second screen to third screen I cannot navigate as it can't find the variable "navigation", which is understandable because I'm using different js files for each screen. How do I pass over the navigation variable to the other screen? Below is my code:
OrderScreen.js:
imports...
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const styles = StyleSheet.create({
...
}
});
function Orderscreen({ navigation }) {
return (
<View style={styles.welcome}>
<View styles={styles.container}>
<Button style={styles.button1} color="warning" uppercase onPress={() => { navigation.navigate('Scan QR code') }}><Text style={styles.buttonText}>Take me to screen SCAN QR CODE</Text></Button>
</View>
</View>
)
};
import { Qrcodescanner } from './qrcodescanner';
import { Menu } from './menu';
import { render } from 'react-dom';
function MenuScreen({ navigation }) {
return(
<Menu></Menu>
)
}
function QRScanScreen({ navigation }) {
return(
<Qrcodescanner></Qrcodescanner>
)
}
export default function App() {
return (
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={Orderscreen} />
<Stack.Screen name="Scan QR code" component={QRScanScreen}
options={{
headerRight: () => (
<Button shadowless
onlyIcon icon="question" iconFamily="antdesign" iconSize={30} color="info" iconColor="#fff" style={{ width: 35, height: 35, marginRight: 20 }}
/>
),
}}
/>
<Stack.Screen name="Menu" component={MenuScreen} />
</Stack.Navigator>
);
}
Qrcodescanner.js:
export class Qrcodescanner extends React.Component {
...
render() {
const { hasCameraPermission, scanned } = this.state;
if (hasCameraPermission === null) {
return <Text> Requesting for camera permission </Text>;
}
if (hasCameraPermission === false) {
return <Text> No access to camera </Text>;
}
return (
<View
style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'flex-end',
}}> ...
</View>
);
}
handleBarCodeScanned = ({ type, data}) => {
this.setState({
scanned: true,
});
alert(`Bar code with type ${type} and data ${data} has been scanned!`);
navigation.navigate('Menu')
};
};
So when I do scan something, it should navigate to the 'Menu' screen but instead I get the error: 'can't find variable: navigation'. How do I pass the navigation variable to a different js file?
i have seen your cod and got a solution for you you just have to create a new file NavigationServices.js
import { NavigationActions } from "react-navigation";
let _navigator;
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
}
// add other navigation functions that you need and export them
export default {
navigate,
setTopLevelNavigator
};
and then import the file in your .js file and use it to navigate like in your OrderScreen.js:
import NavigationService from "../NavigationService";
and use it like this.
NavigationService.navigate("Qrcodescanner");

How to refresh main App class rendering in React-Native from another class

I'm trying to create my first App using React-Native,
i've created a class which renders the authentication form, after handling the submit the App should render the Navigation Screen with its tabs. I think i can "refresh" in someway the App class rendering from the Authentication Screen so it can check again if the user has authenticated or not, but i'm not really sure
App.Js:
import AuthScreen from './screens/AuthScreen';
export default class App extends React.Component {
state = {
isLoadingComplete: false,
isAuthenticated: false,
};
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return (
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
} else {
if(this.state.isAuthenticated == true) {
return (
<View style={styles.container}>
<StatusBar hidden = {true} />
<AppNavigator />
</View>
);
} else {
return (
<View style={styles.container}>
<StatusBar hidden = {true} />
<AuthScreen />
</View>
);
}
}
}
AuthScreen.js:
export default class AuthScreen extends Component {
handleSubmit = () => {
const value = this._form.getValue();
console.log('value: ', value);
}
render() {
return (
<View style={styles.container}>
<View style={styles.auth_container}>
<Form
ref={c => this._form = c}
type={User}
options={options}
/>
<Button
title="Submit"
onPress={this.handleSubmit}
/>
</View>
</View>
);
}
}
You can do this by using react-navigation(RN navigation library). But per code in question your trying to toggle between screen.
In your way: handleSubmit method of AuthScreen if success to following
handleSubmit = () => {
check auth logic
this.props.onSuccessFullLogin(value)
}
Update State in ParentComponent to toggle between screens App Component:
<AuthScreen /> this should be like <AuthScreen onSuccessFullLogin={()=>this.setState({isAuthenticated:true})} />

Why is my custom component not rendering in React Native?

I have created a component called OrderGuideSelect and I am trying to render it in another area of our app. The problem is the OrderGuideSelect component is not rendering. When I set up breakpoints I am able to hit inside of the renderOrderGuideOptions function but it never makes it into the OrderGuideSelect.js file. I also tried putting 'export default' in front of the class declaration instead of the connection but it didn't make a difference. Does anyone know how to get the OrderGuideSelect component rendering properly?
Here is where I call the function that renders the OrderGuideSelect component:
<TouchableOpacity onPress={() => this.renderOrderGuideOptions()}>
<MBIcon name="ico-24-filter" size={30} style={styles.filterIcon}/>
</TouchableOpacity>
And here is the rendering function:
renderOrderGuideOptions = () => {
return (
<View>
<OrderGuideSelect />
</View>
)
}
Here is the OrderGuideSelect.js file:
import React, {Component} from 'react';
import {View, FlatList, ActivityIndicator, StyleSheet} from 'react-native';
import {connect} from 'react-redux';
import {fetchOrderGuides} from '../../actions/AppActions';
import {orderGuideSelected} from '../../actions/ProductAction';
import Header from '../../components/Header/Header';
import {createIconSetFromIcoMoon} from 'react-native-vector-icons';
import selection from '../../selection';
import OrderGuideOption from './OrderGuideOption';
const MBIcon = createIconSetFromIcoMoon(selection);
class OrderGuideSelect extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.dispatch(fetchOrderGuides());
}
selectOrderGuide = id => {
this.props.dispatch(orderGuideSelected(id));
}
render() {
const {isLoading, orderGuides} = this.props.orderGuide;
return (
<View style={styles.wrapper}>
<Header />
<View style={styles.iconLine}>
<MBIcon name='ico-24-filter' style={styles.filterIcon} />
</View>
{isLoading &&
<ActivityIndicator
style={{alignSelf: 'center'}}
animating={true}
size='large'
/>
}
{!isLoading &&
<View style={styles.optionList}>
<FlatList
style={styles.optionList}
data={orderGuides}
keyExtractor={(item, index) => item.id.toString()}
renderItem={({item}) => <OrderGuideOption guideData={item} isSelected={item.id == this.props.selectedGuide.id} onSelected={this.selectOrderGuide} />}
/>
</View>
}
</View>
);
}
}
function mapStateToProps(state){
const {products, orderGuide} = state;
return {
selectedGuide: products.selectedOrderGuide,
orderGuide
}
}
export default connect(mapStateToProps)(OrderGuideSelect);
Also, I may be importing of the OrderGuideSelect component should be correct:
In your code calling this.renderOrderGuideOptions function on onPress event doesn't make sense, i.e. this.renderOrderGuideOptions returns the element but where to append it in DOM?
This should be achived using state in React. So you can set the state in onPress handler then use that state in render to show your OrderGuideOptions component.
So on onPress event bind the function handler:
<TouchableOpacity onPress={this.showOrderGuideOptions}>
<MBIcon name="ico-24-filter" size={30} style={styles.filterIcon}/>
</TouchableOpacity>
Now this showOrderGuideOptions will set the state named showOrderGuideFunction to true.
showOrderGuideOptions(){
this.setState({showOrderGuideFunction: true});
}
At last step use this showOrderGuideFunction state to render your component in the render function like this:
render() {
return (
<div>
...
{
this.state.showOrderGuideFunction &&
renderOrderGuideOptions()
}
</div>
)
}
You can do what you want probably holding a state property in your component and show your OrderGuideOptions according to this state property.
state = { showOrderGuideOptions: false };
renderOrderGuideOptions = () =>
this.setState( prevState => ( { showOrderGuideOptions: !prevState.showOrderGuideOptions }) );
render() {
return (
<View>
<TouchableOpacity onPress={this.renderOrderGuideOptions}>
<MBIcon name="ico-24-filter" size={30} style={styles.filterIcon}/>
</TouchableOpacity>
{ this.state.showOrderGuideOptions && <OrderGuideSelect /> }
</View>
)
}
I think you wanted to something similar to this
class RenderOrderGuideSelectComponent extends Component {
constructor(props) {
super(props);
this.state={
showOrderGuideSelect : false
};
}
renderOrderGuideOptions = () => {
this.setState({showOrderGuideSelect: true});
}
render() {
if(this.state.showOrderGuideSelect) {
return (
);
} else {
return (
this.renderOrderGuideOptions()}>
);
}
}
}

Resources