Multiple drawers in react-navigation 5.0 - react-navigation

I wrote below code to make multiple side menus but it occurred error
Another navigator is already registered for this container. You likely have multiple navigators under a single "NavigationContainer" or "Screen". Make sure each navigator is under a separate "Screen" container.
However I've tried to find the Container for multiple drawers but no luck.
What should I do?
Thanks in advance.
import React from 'react';
import { AppLoading } from 'expo';
import * as Font from 'expo-font';
import { Ionicons } from '#expo/vector-icons';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { NavigationNativeContainer } from '#react-navigation/native';
import { Container, Text, Button } from 'native-base';
import 'react-native-gesture-handler'
function BlankScreen({ navigation }) {
return (
<Text>Blank</Text>
);
}
function HomeScreen({ navigation }) {
return (
<Container style={{ flex: 1, flexDirection: 'column-reverse' }}>
<Button onPress={() => navigation.navigate('Menu')}>
<Text>Go to Menu</Text>
</Button>
<Button onPress={() => navigation.navigate('Favorit')}>
<Text>Go to Favorit</Text>
</Button>
</Container>
);
}
function MenuScreen({ navigation }) {
return (
<Container style={{ flex: 1, flexDirection: 'column-reverse' }}>
<Button onPress={() => navigation.goBack()}>
<Text>Go back home</Text>
</Button>
</Container>
);
}
function FavoritScreen({ navigation }) {
return (
<Container style={{ flex: 1, flexDirection: 'column-inverse' }}>
<Button onPress={() => navigation.goBack()}>
<Text>Go back home</Text>
</Button>
</Container>
);
}
const DrawerL = createDrawerNavigator();
const DrawerR = createDrawerNavigator();
export default function App() {
return (
<Container>
<NavigationNativeContainer>
<DrawerL.Navigator initialRouteName="Home" drawerPosition="left">
<DrawerL.Screen name="Home" component={HomeScreen} />
<DrawerL.Screen name="Menu" component={MenuScreen} />
<DrawerL.Screen name="Favorit" component={FavoritScreen} />
</DrawerL.Navigator>
<DrawerR.Navigator initialRouteName="Blank" drawerPosition="right">
<DrawerR.Screen name="Blank" component={BlankScreen} />
<DrawerR.Screen name="Menu" component={MenuScreen} />
<DrawerR.Screen name="Favorit" component={FavoritScreen} />
</DrawerR.Navigator>
</NavigationNativeContainer>
</Container>
);

The way you wrote it won't work, because consider this: you've 2 drawers, one has initial route as "Home", and other as "Blank". Which one should be rendered?
Here, you need to follow the same approach as React Navigation 4, put one drawer inside another:
function HomeStack() {
return (
<Stack.Navigator screenOptions={{ animationEnabled: false }}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Menu" component={MenuScreen} />
<Stack.Screen name="Favorit" component={FavoritScreen} />
</Stack.Navigator>
)
}
function RightDrawer() {
return (
<DrawerR.Navigator initialRouteName="Home" drawerPosition="right">
<DrawerR.Screen name="Home" component={HomeStack} />
<DrawerR.Screen name="Menu" component={MenuScreen} />
<DrawerR.Screen name="Favorit" component={FavoritScreen} />
</DrawerR.Navigator>
)
}
function LeftDrawer() {
return (
<DrawerL.Navigator initialRouteName="RightDrawer" drawerPosition="left">
<DrawerL.Screen name="RightDrawer" component={RightDrawer} />
</DrawerL.Navigator>
);
}
function App() {
return (
<NavigationNativeContainer>
<LeftDrawer />
</NavigationNativeContainer>
)
}
Since your left drawer won't show the screens that you have in Stack, you will need to provide a custom component for your drawer which lists the screens: https://reactnavigation.org/docs/en/next/drawer-navigator.html#providing-a-custom-drawercontent

Related

React Navigation animation from Drawer to Stack

I have some Drawer navigation
<Drawer.Navigator
screenOptions={
drawerStatus ? screenOptions : {...screenOptions, swipeEnabled: false}
}
drawerContent={props => <CustomDrawer {...props} />}
initialRouteName={'Main'}>
{screens.map(screen => (
<Drawer.Screen
key={screen.name}
name={screen.name}
component={screen.component}
/>
))}
</Drawer.Navigator>
And in this Drawer I have a nested screen "Post Detail"
<Stack.Navigator
screenOptions={{
headerShown: false,
}}>
<Stack.Screen name={'PostDetail'} component={PostDetail} />
</Stack.Navigator>
If I navigate from Drawer to nested StackScreen or go back to Drawer, I don`t catch animation transition.
Apparently, using only the 'createDrawerNavigator', it is not possible to change the screen transition animation. For this, you must create a 'createStackNavigator', inserting your DrawerNavigator in your 'Home'. Do like:
import React from 'react';
import Home from '../pages/home/home';
import HeaderHome from '../shared/headerHome';
import Configuracoes from '../pages/configuracoes';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createStackNavigator } from '#react-navigation/stack';
import { DrawerContent } from '../shared/drawerContent';
const Drawer = createDrawerNavigator();
const Content = (navigation: any) => (
<Drawer.Navigator
drawerContent={props => <DrawerContent {...props} />}
>
<Drawer.Screen name="HomeDrawer" component={Home}
options={{
header: (props) => <HeaderHome value={props} /> //My cystom Header
}}
/>
</Drawer.Navigator>
);
const Stack = createStackNavigator();
const MyStack: React.FC = () => {
return (
<Stack.Navigator
screenOptions={({ route, navigation }) => ({
headerMode: "float",
animationEnabled: true
})}
>
<Stack.Screen name="Home" component={Content} //The name used cannot be identical to the one used in Drawer.Navigator
options={{ headerShown: false }}
/>
<Stack.Screen name="Configuracoes" component={Configuracoes}
options={{
title: "Title page",
headerStyle: {
backgroundColor: "red",
},
headerTintColor: "#fff"
}}
/>
</Stack.Navigator>
);
}
export default MyStack;

React navigation custom header doesn't disappear when navigating away from screen (iOS only)

I have a stack navigator where one of the screen uses a custom header:
import { createStackNavigator } from "#react-navigation/stack";
import * as React from "react";
import { Button, View } from "react-native";
const Stack = createStackNavigator();
function ScreenA({ navigation }) {
return (
<View style={{ flex: 1, justifyContent: "center"}}>
<Button title="Click me" onPress={() => navigation.navigate("ScreenB")} />
</View>
);
}
function ScreenB({ navigation }) {
return (
<View style={{ flex: 1 , justifyContent: "center"}}>
<Button title="Click me" onPress={() => navigation.navigate("ScreenA")} />
</View>
);
}
function TestComp() {
return (
<Stack.Navigator>
<Stack.Screen
name="ScreenA"
component={ScreenA}
options={{ header: () => <View style={{ height: 160, backgroundColor: "red" }}></View> }}
/>
<Stack.Screen name="ScreenB" component={ScreenB} />
</Stack.Navigator>
);
}
export default TestComp;
As a result, the header of ScreenA (a red bar) is visible from ScreenB. This doesn't happen on Android where the header is properly shown ONLY on ScreenA.
How can I stop the header from ScreenA from showing on ScreenB?
Solved it by using <Stack.Navigator headerMode="screen"> !

React Native Stack Navigator passing props

I want to pass some props (value) to another page and I'm using stackNavigator
App.js:
import Insert from "./components/pages/Insert";
import ViewData from "./components/pages/ViewData";
const Stack = createStackNavigator();
NavigationContainer>
<Stack.Navigator headerMode={false}>
<Stack.Screen name="Insert Your data" component={Insert} />
<Stack.Screen name="ViewData" component={ViewData} />
</Stack.Navigator>
</NavigationContainer>
Insert.js
const Insert = ({ props, navigation: { navigate } }) => {
const [enteredName, setEnteredName] = useState();
const [enteredSurname, setEnteredSurname] = useState();
const sendValues = (enteredName, enteredSurname) => {
setEnteredName(enteredName);
setEnteredSurname(enteredSurname);
navigate("ViewData", {
name: enteredSurname,
surname: enteredSurname
});
};
...
<View>
<Button
title="Submit"
onPress={() => sendValues(enteredName, enteredSurname)}
/>
ViewData.js
const ViewData = ({props, navigation: { goBack } }) => {
let name = enteredName;
console.log(name); /// I always get undefined
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Here {name}</Text>
<Button onPress={() => goBack()} title="Edit Data" />
</View>
);
};
When I Submit I'm always getting undefined into the console.
For sure I'm mistaking somewhere.
Any idea?
Thanks!!
Refer to https://reactnavigation.org/docs/hello-react-navigation/#passing-additional-props
Use a render callback for the screen instead of specifying a component
prop:
<Stack.Screen name="Home">
{props => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>
You can change like this
import Insert from "./components/pages/Insert";
import ViewData from "./components/pages/ViewData";
const Stack = createStackNavigator();
<NavigationContainer>
<Stack.Navigator headerMode={false}>
<Stack.Screen name="Insert Your data">
{props => (<Insert {...props} extraData={data}/>)}
<Stack.Screen name="ViewData" component={ViewData} />
</Stack.Navigator>
</NavigationContainer>
Reading your code it seems that you want to pass some params to the render component when navigate.
Refer to https://reactnavigation.org/docs/route-prop , the params from
navigation.navigate(routeName, params)
are passed to the screen render component as propriety of the route object. So in your case it should work:
let name = props.route.params.name;

Having issue to create navigation between screen

I have a splash screen which goes to the Intro page after a few seconds and from the Intro page, I have a link for sign in and sign up. I tried to set up navigation from the Intro page to/from Login but encounter an error which says:
undefined is not an object (evaluating '_this.props.navigation.navigate').
I have tried to check the documentation on react-navigation but not clear with the explanation as they muddled up all screens in App.js.
App.js
import React, {Component} from 'react';
import { StyleSheet, StatusBar, Text, View} from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import Intro from './screen/Intro';
import Login from './components/Login';
const RootStack = createStackNavigator(
{
IntroScreen: Intro,
LoginScreen: Login,
},
{
initialRouteName: 'IntroScreen',
}
);
const AppNavigator = createAppContainer(RootStack);
export default class App extends Component {
render() {
return (
<View style={styles.container}>
<StatusBar
barStyle="light-content"
backgroundColor="#fefefe"
/>
<Intro/>
</View>
);
}
}
Intro.js
The page follows the splash screen display and from the page I can select login or sign up.
import React, { Component } from 'react';
import {
Text,
Image,
View,
ImageBackground,
StyleSheet,
TouchableOpacity
}
from 'react-native';
import SplashScreen from 'react-native-splash-screen';
export default class Intro extends Component{
componentDidMount(){
SplashScreen.hide();
}
render(){
return(
<ImageBackground source={require('../images/signup-background.jpg')} style={{width: '100%', height: '100%'}}>
<View style={styles.container}>
<Image
source ={ require('../images/logo.png')}
/>
<Text style={styles.simpleText}>WELCOME</Text>
<Text style={styles.literal}>Thank you for your interest in the APG app. </Text>
<Text style={styles.literal}>How can we help you?</Text>
<TouchableOpacity
style= {styles.signinCont}
onPress={() => this.props.navigation.navigate('IntroScreen')}
>
<Text style= {styles.signinText}>
I am already an APG Patient
</Text>
</TouchableOpacity>
<TouchableOpacity style= {styles.signupCont}>
<Text style= {styles.signupText}>
I am not an APG Patient
</Text>
</TouchableOpacity>
</View>
</ImageBackground>
);
}
}
Login.js
On the Login, I have a button to link to Signup and forgot password in case the user doesn't have an account
import React, { Component } from 'react';
import {
Text,
TouchableOpacity,
Label,
View,
TextInput,
StyleSheet,
}
from 'react-native';
export default class Login extends Component {
render(){
return(
<View style={styles.container}>
<View>
<Text>Login</Text>
</View>
<Label>Email</Label>
<TextInput
style={Styles.textbox}
placeholder="Email"
value= {this.state.email}
/>
<Label>Password</Label>
<TextInput
style={Styles.textbox}
placeholder="Password"
value ={this.state.password}
secureTextEntry={true}
/>
<View>
<Text>Forgot password</Text>
</View>
<TouchableOpacity style={styles.signin}>
<Text style ={styles.signinText}>Sign In</Text>
</TouchableOpacity>
<View>
<Text>Not an APG Member? </Text>
<TouchableOpacity style={styles.signup}
onPress ={() => this.props.navigation.navigate('SignUp') }
>
<Text>Sign Up</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
I want to be able to choose either the login or signup as the case may be from the Intro.js and on login.js to be able to click on signup for registration and from signup to be able to click login.
Solution
onPress={this.props.navigation.navigate('SignUp')} do not invoke this.props.navigation.navigate function.
Change your onPress property as below.
Use es6 arrow function for making anonymous function.
<TouchableOpacity
style={styles.signup}
onPress={() => this.props.navigation.navigate('SignUp')}
>
or you can reference your function in onPress. It makes performance better when render again.
navigate = () => {
this.props.navigation.navigate('SignUp')
}
return (
...
<TouchableOpacity
style={styles.signup}
onPress={this.navigate}
>
)
Why
props only references value even if it is function. So make props reference anonymous function or funtion in class you predefined.
And do not forget bind(this) when you use not arrow function.
navigate() {
this.props.navigation.navigate('SignUp')
}
...
onPress={this.navigate.bind(this)}
...

React-Native refs undefined on text input

we are currently running React-Native 0.33
We are trying to use the refs to go from 1 text input to another when they hit the next button. Classic username to password.
Anyone have any idea? Below is the code we are using. From other posts I've found on stack this is what they've done; however, it's telling us that this.refs is undefined.
UPDATE
So I've narrowed the problem down to
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'transparent'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
If I just render the code below in the renderScene function inside of the original render it works, however with the navigator it won't work. Does anyone know why? Or how to have the navigator show as well as render the code in renderScene to appear in the original render?
class LoginIOS extends Component{
constructor(props) {
super(props);
this.state={
username: '',
password: '',
myKey: '',
};
}
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={{backgroundColor: 'transparent'}}
routeMapper={NavigationBarRouteMapper} />
} />
);
}
renderScene() {
return (
<View style={styles.credentialContainer}>
<View style={styles.inputContainer}>
<Icon style={styles.inputPassword} name="person" size={28} color="#FFCD00" />
<View style={{flexDirection: 'row', flex: 1, marginLeft: 2, marginRight: 2, borderBottomColor: '#e0e0e0', borderBottomWidth: 2}}>
<TextInput
ref = "FirstInput"
style={styles.input}
placeholder="Username"
autoCorrect={false}
autoCapitalize="none"
returnKeyType="next"
placeholderTextColor="#e0e0e0"
onChangeText={(text) => this.setState({username: text})}
value={this.state.username}
onSubmitEditing={(event) => {
this.refs.SecondInput.focus();
}}
>
</TextInput>
</View>
</View>
<View style={styles.inputContainer}>
<Icon style={styles.inputPassword} name="lock" size={28} color="#FFCD00" />
<View style={{flexDirection: 'row', flex: 1, marginLeft: 2, marginRight: 2, borderBottomColor: '#e0e0e0', borderBottomWidth: 2}}>
<TextInput
ref = "SecondInput"
style={styles.input}
placeholder="Password"
autoCorrect={false}
secureTextEntry={true}
placeholderTextColor="#e0e0e0"
onChangeText={(text) => this.setState({password: text})}
value={this.state.password}
returnKeyType="done"
onSubmitEditing={(event)=> {
this.login();
}}
focus={this.state.focusPassword}
>
</TextInput>
</View>
</View>
</View>
);
}
Try setting the reference using a function. Like this:
<TextInput ref={(ref) => { this.FirstInput = ref; }} />
Then you can access to the reference with this.FirstInput instead of this.refs.FirstInput
For a functional component using the useRef hook. You can use achieve this easily, with...
import React, { useRef } from 'react';
import { TextInput, } from 'react-native';
function MyTextInput(){
const textInputRef = useRef<TextInput>(null);;
return (
<TextInput ref={textInputRef} />
)
}
Try changing the Navigator's renderScene callback to the following (based on Navigator documentation) cause you will need the navigator object later.
renderScene={(route, navigator) => this.renderScene(route, navigator)}
Then, use 'navigator' instead of 'this' to get the refs.
navigator.refs.SecondInput.focus()

Resources