How to Implement Dynamic Menu (Drawer) items in React Native - javascript

this is the scenario:
In Home Route, there is a dropdown which contains SubSystems and based on each subSystem menu items are different. So, when I change subSystem, Menu Items of drawer should update.
I have implemented my drawer to get dynamic items like below code. But, the problem is that when then SubSystems are loaded I go to get the menu items after that I update the menuData state the whole component recompiles and it falls into a loop.
How Can I Overcome This Problem ?
MY CODE
StackNavigationManager.js
import {AuthContext} from './context';
const StackNavigationManager = () => {
const [menuData, setMenuData] = React.useState([]);
const authContext = React.useMemo(() => ({
validateMenuData: menuData => {
setMenuData(menuData);
},
}));
const DrawerScreen = ({route, navigation}) => (
<Drawer.Navigator
drawerContent={props => <CustomDrawerContent {...props} />}>
<Drawer.Screen
name="HomeDrawer"
component={Home} />
</Drawer.Navigator>
);
function CustomDrawerContent(props) {
return (
<SafeAreaView style={{flex: 1}}>
<DrawerItemList {...props} />
<FlatList
data={menuData}
keyExtractor={data => data.id.toString()}
renderItem={({item}) => {
return (
<DrawerItem
label={item.name}
onPress={() => alert(item.id)}></DrawerItem>
);
}}
/>
</SafeAreaView>
);
}
return (<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator
initialRouteName={initialRoute}>
<Stack.Screen
name="Home"
component={DrawerScreen}
options={{
headerShown: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>);
Context.js
import React from 'react';
export const AuthContext = React.createContext();
Home.js
import {AuthContext} from './context';
const home = ({navigation}) => {
const {validateMenuData} = React.useContext(AuthContext);
React.useEffect(() => {
console.log('MOUNTED EFFECT');
getSubSystems();
return () => {
isMounted = false;
console.log('UNMOUNTED EFFECT');
};
}, []);
const getSubSystems = async () => {
const subSystemModel = {
isActive: true,
isVisible: true,
};
fetchSubSystemData(
subSystemModel,
response => {
if (response.list !== null) {
let listData = [...response.list];
fetchMenuAction(
listData[0].id,
response2 => {
validateMenuData([...response2.list]);
},
);
}
},
() => {},
);
};

Related

How to navigate from linking (deep linking with branch.io) when navigator hasn't been created yet?

I pretty much followed both react-navigation deep linking and branch.io react-native documentation, and either both are deprecated or just not completely helpful.
All I want is that whenever a deep link reads from the linking, navigate to a certain screen, I'm not looking to implement a listener on a specific screen, I want this on a root path, and is either the onReady (which for me didn't work) or linking from navigator container
this is my code, very simple
const linking: LinkingOptions = {
prefixes: ['agendameio://', 'https://agendame.io', 'https://agendameio.app.link', 'https://agendameio.app-alternative.link'],
subscribe(listener) {
const navigation = useNavigation();
const onReceiveURL = ({ url }: { url: string }) => listener(url);
Linking.addEventListener('url', onReceiveURL);
branch.skipCachedEvents();
branch.subscribe(async ({ error, params, uri }) => {
if (error) {
console.error('Error from Branch: ' + error);
return;
}
if (params) {
DataManager.DynamicURL = params['~id'] === "951933826563912687" ? params.id : undefined;
}
let url = params?.['+url'] || params?.['~referring_link']; // params !== undefined ? `agendameio://empresa/${params.id}` : 'agendameio://empresa';
navigation.navigate(`DetalleEmpresa${params.id}`);
listener(url);
});
return () => {
Linking.removeEventListener('url', onReceiveURL);
branch.logout();
};
},
I instantly get an error due to use navigation, but I really don't know what else to use to navigate to inside the app
EDIT: this is the error in particular
EDIT 2: I'll add my navigation so it can help to understand my problem
function firstStack() {
return (
<homeStack.Navigator initialRouteName="EmpresasScreen">
<homeStack.Screen
options={({navigation}) => ({
headerShown: false,
headerTitle: () => (
<>
<View style={styles.viewHeader}>
<Image
resizeMode="contain"
style={styles.imageLogo}
source={Images.iconoToolbar}
/>
</View>
</>
),
})}
name="EmpresasScreen"
component={EmpresasScreen}
/>
<detalleEmpresaStack.Screen
options={{ headerShown: false }}
name="DetalleEmpresaScreen"
component={DetalleEmpresaScreen}
/>
<agendamientoStack.Screen
options={{ headerShown: false }}
name="AgendamientoScreen"
component={AgendamientoScreen}
/>
</homeStack.Navigator>
);
}
function secondStack() {
return (
<misCitasStack.Navigator>
<misCitasStack.Screen
options={({navigation}) => ({
headerShown: false,
headerTitle: () => (
<>
<View style={styles.viewHeader}>
<Image
resizeMode="contain"
style={styles.imageLogo}
source={Images.iconoToolbar}
/>
</View>
</>
),
})}
name="MisCitasScreen"
component={CitasScreen}
/>
<detalleCitasStack.Screen
options={({navigation}) => ({
headerShown: false,
})}
name="DetalleCitaScreen"
component={DetalleCitaScreen}
/>
</misCitasStack.Navigator>
);
}
function tabStack() {
return (
<tab.Navigator
screenOptions={({route}) => ({
tabBarIcon: ({focused}) => {
let iconName;
if (route.name === 'Home') {
iconName = focused
? Images.casaActive
: Images.casa
} else if (route.name === 'Citas') {
iconName = focused
? Images.citasActive
: Images.citas
}
return <Image source={iconName} />
}
})}
tabBarOptions={{
showLabel: false,
}}>
<tab.Screen name="Home" component={firstStack} />
<tab.Screen name="Citas" component={secondStack} />
</tab.Navigator>
);
}
function menuStackNavigator() {
useEffect(() => {
VersionCheck.needUpdate({forceUpdate: true}).then(async res => {
if (res.isNeeded) {
alertNeedUpdate(res.storeUrl, false);
}
});
if(Platform.OS === 'android') {
NativeModules.SplashScreenModule.hide();
}
}, [])
return (
<NavigationContainer linking={linking}>
<stack.Navigator headerMode="none">
<stack.Screen name="Home" component={tabStack} />
<stack.Screen name="Error" component={ErrorScreen} />
</stack.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
viewHeader: {
alignItems: 'center',
justifyContent: 'center',
},
imageLogo: {
alignItems: 'center',
justifyContent: 'center',
marginTop: 6,
marginBottom: 6
}
});
export default menuStackNavigator;
you can use Configuring links to open the target screen directly.
see more example here configuring-links
Here the URL /feed will open screen named Chat.
import { NavigationContainer } from '#react-navigation/native';
const linking = {
prefixes: ['https://mychat.com', 'mychat://'],
config: {
screens: {
Chat: 'feed/:sort', //URL `/feed` will open screen named `Chat`.
Profile: 'user',
}
},
};
function App() {
return (
<NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
<Stack.Navigator>
<Stack.Screen name="Chat" component={ChatScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
or use navigationRef.
read about it navigating-without-navigation-prop.
wait to navigation to be ready and then navigate.
import { createNavigationContainerRef } from '#react-navigation/native';
function App() {
const navigationRef = createNavigationContainerRef();
const navigateWhenNavigationReady = (routeName, params, n = 0) => {
setTimeout(() => {
if (navigationRef?.getRootState()) {
navigationRef.navigate(routeName, params)
}else if (n < 100) {
navigateWhenNavigationReady(routeName, params, n + 1);
}
}, 300)
}
const linking = {
...,
subscribe(listener) {
...
navigateWhenNavigationReady("Chat", {id: 123});
}
};
return (
<NavigationContainer ref={navigationRef} linking={linking}>
<Stack.Navigator>
<Stack.Screen name="Chat" component={ChatScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
can you try doing it like this
const App = () => {
return (
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
);
};
and do the subscribe in
export const AppNavigator = () => {
const navigation =useNavigation();
useEffect(()=>{
//add listener and navigation logic here
},[]);
return (
<Stack.Navigator >
...
</Stack.Navigator>
);
};
this will make navigation context to be available in AppNavigator Component
Answer use custom hook useNavigationWhenReady.
const useNavigationWhenReady = (isReady, navigationRef) => {
const [routeName, setRouteName] = React.useState();
const [routeParams, setRouteParams] = React.useState({});
const [navigationAction, setNavigationAction] = React.useState("navigate");
React.useEffect(() => {
if (isReady && routeName) {
if(navigationRef && navigationRef[navigationAction]) {
const _navigationAction = navigationRef[navigationAction];
_navigationAction(routeName, routeParams);
}
}
}, [isReady, routeParams, routeParams]);
const navigate = (_routeName, _routeParams = {}) => {
if(!routeName) {
setNavigationAction("navigate");
setRouteParams(_routeParams);
setRouteName(_routeName);
}
};
const reset = (state) => {
if(!routeName) {
setNavigationAction("reset");
setRouteName(state);
}
};
return { navigate, reset }
};
you can now use useNavigationWhenReady instead of useNavigation;
import { createNavigationContainerRef } from '#react-navigation/native';
function App() {
const [isReady, setReady] = React.useState(false);
const navigationRef = createNavigationContainerRef();
//define it here
const navigation = useNavigationWhenReady(isReady, navigationRef);
const handleOpenNotificationOrOpenLinking = () => {
...
navigation.navigate("screenName", {param1: value1});
//or use reset
//navigation.reset({
//index: 1,
//routes: [{ name: 'screenName' }]
//});
};
return (
<NavigationContainer ref={navigationRef} onReady={() => setReady(true)}>
</NavigationContainer>
);
}
if you use react-navigation-v5 not v6 use
//const navigationRef = createNavigationContainerRef();
//const navigation = useNavigationWhenReady(isReady, navigationRef);
const navigationRef = React.useRef();
const navigation = useNavigationWhenReady(isReady, navigationRef?.current);
you can also show loading or splash screen while navigation not ready
return (
<NavigationContainer ref={navigationRef} onReady={() => setReady(true)}>
<RootStack.Navigator initialRouteName={isReady ? "home" : "loading"} >
</RootStack>
</NavigationContainer>
);

The action 'NAVIGATE' with payload {“name”:“HomeScreen”,“params”:…..."} was not handled by any navigator. Do you have a screen named 'Home'?

The action 'NAVIGATE' with payload {"name":"Home","params":{"user":{"id":"VUUpROQPtKaXVef15e5XhxXNLrm1","email":"anto22e#gmail.com","fullName":"snertp0"}}} was not handled by any navigator.
Do you have a screen named 'Home'?
I have this problem when I try to log in or register, I don't know how to solve it. I have tried several ways that I have found but I can't find the solution, Thank you!
This is all the code I use, so I don't understand why you tell me there is no Screen Home, if I am using it. I've been checking several times all the code but I can't find where that fault may be, since I think I'm calling it all correctly.
LoginScreen
import React, {useState} from 'react';
import {Image, Text, TextInput, TouchableOpacity, View} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import {app} from '../firebase/DataBaseConfig';
import HomeScreen from './HomeScreen';
export default function LoginScreen({navigation}) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const onFooterLinkPress = () => {
navigation.navigate('Registration');
};
const onLoginPress = () => {
app
.auth()
.signInWithEmailAndPassword(email, password)
.then((response) => {
const uid = response.user.uid;
const usersRef = app.firestore().collection('users');
usersRef
.doc(uid)
.get()
.then((firestoreDocument) => {
if (!firestoreDocument.exists) {
alert('User does not exist anymore.');
return;
}
const user = firestoreDocument.data();
navigation.navigate("HomeScreen", {user});
})
.catch((error) => {
alert(error);
});
})
.catch((error) => {
alert(error);
});
};
return (
<KeyboardAwareScrollView
style={{flex: 1, width: '100%'}}
keyboardShouldPersistTaps="always">
<TextInput
placeholder="E-mail"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setEmail(text)}
value={email}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TextInput
placeholderTextColor="#aaaaaa"
secureTextEntry
placeholder="Password"
onChangeText={(text) => setPassword(text)}
value={password}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity onPress={() => onLoginPress()}>
<Text>Log in</Text>
</TouchableOpacity>
<View>
<Text>
Don't have an account?{' '}
<Text onPress={onFooterLinkPress}>Sign up</Text>
</Text>
</View>
</KeyboardAwareScrollView>
);
}
App.js
import React, {useEffect, useState} from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import LoginScreen from './components/screens/LoginScreen';
import HomeScreen from './components/screens/HomeScreen';
import RegistrationScreen from './components/screens/RegistrationScreen';
import {decode, encode} from 'base-64';
import {firebase} from './components/firebase/DataBaseConfig';
if (!global.btoa) {
global.btoa = encode;
}
if (!global.atob) {
global.atob = decode;
}
const Stack = createStackNavigator();
export default function App() {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState(null);
return (
<NavigationContainer>
<Stack.Navigator>
{user ? (
<Stack.Screen name="HomeScreen">
{(props) => <HomeScreen {...props} extraData={user} />}
</Stack.Screen>
) : (
<>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Registration" component={RegistrationScreen} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
if (loading) {
return <></>;
}
useEffect(() => {
const usersRef = firebase.firestore().collection('users');
firebase.auth().onAuthStateChanged((user) => {
if (user) {
usersRef
.doc(user.uid)
.get()
.then((document) => {
const userData = document.data();
setLoading(false);
setUser(userData);
})
.catch((error) => {
setLoading(false);
});
} else {
setLoading(false);
}
});
}, []);
}
HomeScreen
import React, {useEffect, useState} from 'react';
import {
FlatList,
Keyboard,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
import styles from '../styles/styles';
import {firebase} from '../../components/firebase/DataBaseConfig';
export default function HomeScreen(props) {
const [entityText, setEntityText] = useState('');
const [entities, setEntities] = useState([]);
const entityRef = firebase.firestore().collection('entities');
const userID = props.extraData.id;
useEffect(() => {
entityRef
.where('authorID', '==', userID)
.orderBy('createdAt', 'desc')
.onSnapshot(
(querySnapshot) => {
const newEntities = [];
querySnapshot.forEach((doc) => {
const entity = doc.data();
entity.id = doc.id;
newEntities.push(entity);
});
setEntities(newEntities);
},
(error) => {
console.log(error);
},
);
}, []);
const onAddButtonPress = () => {
if (entityText && entityText.length > 0) {
const timestamp = firebase.firestore.FieldValue.serverTimestamp();
const data = {
text: entityText,
authorID: userID,
createdAt: timestamp,
};
entityRef
.add(data)
.then((_doc) => {
setEntityText('');
Keyboard.dismiss();
})
.catch((error) => {
alert(error);
});
}
};
const renderEntity = ({item, index}) => {
return (
<View style={styles.entityContainer}>
<Text style={styles.entityText}>
{index}. {item.text}
</Text>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.formContainer}>
<TextInput
style={styles.input}
placeholder="Add new entity"
placeholderTextColor="#aaaaaa"
onChangeText={(text) => setEntityText(text)}
value={entityText}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<TouchableOpacity style={styles.button} onPress={onAddButtonPress}>
<Text style={styles.buttonText}>Add</Text>
</TouchableOpacity>
</View>
{entities && (
<View style={styles.listContainer}>
<FlatList
data={entities}
renderItem={renderEntity}
keyExtractor={(item) => item.id}
removeClippedSubviews={true}
/>
</View>
)}
</View>
);
}´´´
in your App.js, you have the following:
<Stack.Screen name="Home">
{(props) => <HomeScreen {...props} extraData={user} />}
</Stack.Screen>
it should be
<Stack.Screen name="HomeScreen">
{(props) => <HomeScreen {...props} extraData={user} />}
</Stack.Screen>
then you need to make the following changes to the navigator:
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={user ? 'HomeScreen' : 'Login'}>
<Stack.Screen name="HomeScreen">
{(props) => <HomeScreen {...props} extraData={user} />}
</Stack.Screen>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Registration" component={RegistrationScreen} />
)}
</Stack.Navigator>
</NavigationContainer>
);
on your login screen change
navigation.navigate(HomeScreen, {user});
to
navigation.navigate("HomeScreen", {user});

React Native, how do I pass params to a screen when pressing the tab in a tab navigator?

I have a function using the stack navigator, and inside of that is a nested tab navigator. After obtaining a code on the "ConnectScreen" I want to pass the code to "FeedScreen" inside of the tab navigator. The code is accessible as route.params.code in the Home function. I then want to pass the code to FeedScreen.
function ConnectScreen({ navigation, route }) {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'xxx',
scopes: [
"user-top-read",
"user-read-currently-playing",
"user-read-playback-state",
"user-modify-playback-state",
],
usePKCE: false,
redirectUri: makeRedirectUri({
native: 'your.app://redirect',
}),
},
discovery
);
const [state, setState] = React.useState({
token: null
})
React.useEffect(() => {
if (response?.type === 'success') {
var clientId = 'xxx'
var clientSecret = 'xxx'
var credsB64 = btoa(`${clientId}:${clientSecret}`)
var accessCode = response.params.code
var redirectUri = "xxx"
}
const tokenResponse = fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
headers: {
Authorization: `Basic ${credsB64}`,
},
body: `grant_type=authorization_code&code=${accessCode}&redirect_uri=${redirectUri}`,
}).then((response) => response.json())
.then((responseJSON) => {
if (responseJSON.access_token) {
var token = responseJSON.access_token
setState({
token: token
})
}
})
}, [response]);
return (
<View style={styles.container}>
<Button
disabled={!request}
title="Connect to Spotify"
onPress={() => {
promptAsync()
}}
/>
<Button
title="Home"
onPress={() =>
navigation.navigate('Home', {
screen: 'FeedScreen',
params: {
code: state.token,
}
})
}
/>
</View>
);
}
class FeedScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
}
const HomeTab = createBottomTabNavigator();
function Home({ route}) {
console.log("HOME TAB ROUTE",route.params.code)
return (
<HomeTab.Navigator>
<HomeTab.Screen name= "Feed" component={FeedScreen} />
<HomeTab.Screen name= "Player" component={PlayerScreen} />
<HomeTab.Screen name= "Search" component={SearchScreen} />
</HomeTab.Navigator>
)
}
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Splash"
component={SplashScreen}
/>
<Stack.Screen
name="Login"
component={LoginScreen}
/>
<Stack.Screen
name="Connect"
component={ConnectScreen}
/>
<Stack.Screen
name="Home"
component={Home}
/>
</Stack.Navigator>
</NavigationContainer>
You can navigate to FeedScreen directly from ConnectScreen
navigation.navigate('Home', { screen: 'FeedScreen ', params: {code: yourCode} });
Try this way
<HomeTab.Screen name="home" children={()=><ComponentName propName={propValue}/>} />
You can checkout react-navigation for navigation actions. Basically, you can create specific methods for specific actions Stack and Navigation based.
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const Stack = createStackNavigator();
const MyStack = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
You can find StackActions: StackActions
You can find NavigationActions: NavigationActions
I prefer NavigationService.js file to navigate and manage the stack.
// NavigationService.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,
};

Props through React Navigation Drawer

I`m developing an APP for a championship and I have a HomeScreen where the Manager should LOGIN so he/she can update the results.
My problems is: I have a Drawer Navigator and a static password.
How can I get the prop isAdmin to the other screens??
Situation Example:
Screen named 'Points' has a Text Input which enabled status will depend on a function that evaluates if isAdmin= true.
HomeScreen:
export default function Login(){
const [senha, setSenha] = useState('');
var isAdmin = false;
function passCheck(pass){
if(pass == 'Adminlaje'){
isAdmin=true;
alert('Signed In !')
}
else
alert('Wrong password !');
}
return(
<SafeAreaView>
<TextInput
value = {senha}
onChangeText = { (senha) => setSenha(senha) }
/>
<TouchableOpacity onPress = {() => passCheck(senha)}>
<Text>Entrar</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
My Drawer:
const Screens = ({navigation}) =>{
return(
<Stack.Navigator>
<Stack.Screen name = 'HomeScreen' component ={Login}
options={{
headerLeft: () =>(
<TouchableOpacity style = {styles.button} onPress = {()=> navigation.toggleDrawer()}>
<Entypo name="menu" size={35} color="black" />
</TouchableOpacity>
)
}}>
</Stack.Screen>
<Stack.Screen name = 'Points' component ={Points}
options={{
headerLeft: () => (
<TouchableOpacity style = {styles.button} onPress = {()=> navigation.toggleDrawer()}>
<Entypo name="menu" size={35} color="black" />
</TouchableOpacity>
),
}}
>
</Stack.Screen>
</Stack.Navigator>
);
};
const DrawerContent = (props) => {
return(
<DrawerContentScrollView {...props}>
<SafeAreaView>
<DrawerItem
label='HomeScreen'
onPress = {() => props.navigation.navigate('Login')}
/>
<DrawerItem
label='Points'
onPress = {() => props.navigation.navigate('Points')}
/>
</SafeAreaView>
</DrawerContentScrollView>
)
}
export default () => {
return(
<Drawer.Navigator
initialRouteName = 'Login'
drawerContent={props => <DrawerContent {...props}/>}
>
<Drawer.Screen name = 'Screens' component ={Screens}/>
</Drawer.Navigator>
);
};
The answer might depend on what versions of react-navigation and react-navigation-drawer you are using. On a project using "react-navigation": "^4.0.10", and "react-navigation-drawer": "^2.3.3" this is what works for me:
.............
import { NavigationEvents, NavigationActions } from 'react-navigation';
export default class HomeScreen extends Component {
..........
constructor(props){
super(props)
this.state = {language: 'English', menuOpen: false};
}
...................
render(){
const { navigation } = this.props;
const setParamsAction = NavigationActions.setParams({
params: { isAdmin: this.state.isAdmin },
key: 'PointsScreen',
});
const setParamsTwo = NavigationActions.setParams({
params: { isAdmin: this.state.isAdmin },
key: 'OtherScreen',
});
return(
<View style={styles.container}>
<NavigationEvents
onDidBlur={payload => {this.setState({menuOpen: true})}}
onWillFocus={payload => {this.setState({menuOpen: false});
this.backHandler = BackHandler.addEventListener('hardwareBackPress', () =>
{
this.props.navigation.dispatch(setParamsAction);
this.props.navigation.dispatch(setParamsTwo);
this.props.navigation.navigate('PointsScreen');
return true;
})}}
/>
In this example I'm sending the info to other screens when I hit backhandler, but it works the same way for any other navigation event. So for example if you want to add this to your drawer open button you would just substitute this.props.navigation.openDrawer(); for this.props.navigation.navigate('PointsScreen');.
In other screens I have:
.............
render(){
const { navigation } = this.props;
const isAdmin = navigation.getParam('isAdmin') || '';
In the drawer screen I have:
render(){
const { navigation } = this.props;
const pointsProps = navigation.getChildNavigation('PointsScreen');
const isAdmin = pointsProps.getParam('isAdmin') || "";

React Native two drawers on one screen

I have an app that needs to be able to use two drawer navigators, one on the left and on on the right side of the header.
I am at the point where I can get both drawers to open with the slide gesture, however I need to be able to open it programmatically. I have found the navigation.openDrawer() function only works with one of the drawers and not the other because it is only able to use one of the navigation props (whichever comes first) from my drawer navigators.
Below are my rendering functions:
const LeftStack = createStackNavigator(
{
LeftDrawerStack
},
{
navigationOptions: ({navigation}) => ({
headerLeft: (
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon style={{marginLeft: 10}} name='menu'/>
</TouchableOpacity>
)
})
}
);
const RightStack = createStackNavigator(
{
RightDrawerStack
},
{
navigationOptions: ({navigation}) => ({
headerRight: (
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon style={{marginRight: 10}} name='ios-bulb'/>
</TouchableOpacity>
)
})
}
);
export const RouteStack = createStackNavigator(
{
screen: LoginScreen,
navigationOptions: ({navigation}) => ({
header: null
}),
LeftStack,
RightStack
}
);
and here are my drawer routes:
export const LeftDrawerStack = createDrawerNavigator(
{
Dashboard: {
screen: DashboardScreen
},
History: {
screen: HistoryScreen
},
Privacy: {
screen: PrivacyPolicyScreen
},
TermsAndConditions: {
screen: TermsAndConditionsScreen
}
}, {
initialRouteName: 'Dashboard',
contentComponent: LeftDrawerScreen
}
);
export const RightDrawerStack = createDrawerNavigator(
{
LeftDrawerStack,
Settings: {
screen: SettingsScreen
}
}, {
drawerPosition: 'right',
contentComponent: RightDrawerScreen
}
);
Here is a picture of what I have the navigation looking like so far, however both of the hamburger menus are opening up the same menu on the right instead of one menu on their respective sides.
I may be missing some parts but I will be sure to post more info if I forgot any!
I was able to do this with the following setup (try to make changes to your structure to be like this):
const LeftDrawer = createDrawerNavigator(
{
LeftDrawer: MyStackNavigator,
},
{
getCustomActionCreators: (route, stateKey) => { return { toggleLeftDrawer: () => DrawerActions.toggleDrawer({ key: stateKey }) }; },
drawerPosition: 'left',
contentComponent: MyLeftDrawer
}
);
const RightDrawer = createDrawerNavigator(
{
Drawer: LeftDrawer,
},
{
getCustomActionCreators: (route, stateKey) => { return { toggleRightDrawer: () => DrawerActions.toggleDrawer({ key: stateKey }) }; },
drawerPosition: 'right',
contentComponent: MyRightDrawer
}
);
export const RootNavigator = createStackNavigator(
{
Login: Login,
Home: RightDrawer
},
{
initialRouteName: 'Login',
navigationOptions: { header: null, gesturesEnabled: false }
}
);
The key is getCustomActionCreators. It allows you to call the function from any screen in MyStackNavigator like this: this.props.navigation.toggleLeftDrawer();.
This is solution I do
Step 1: Create two drawer navigation and nest them together
Step 2: Store first drawer navigation in other place (Singleton)
// Drawer 1
import ContentLeftMenu from '#components/ContentLeftMenu';
import { createDrawerNavigator } from '#react-navigation/drawer';
import * as React from 'react';
import DrawerCart from './DrawerCart';
import { setNavigationDrawerHome } from './RootNavigation';
import { isTablet } from 'react-native-device-info';
import MainStack from './MainStack';
const Drawer = createDrawerNavigator();
export default function DrawerHome() {
return (
<Drawer.Navigator
initialRouteName="DrawerCart"
drawerContent={() => <ContentLeftMenu />}
screenOptions={({ navigation, route }) => {
setNavigationDrawerHome(navigation)
}}
>
<Drawer.Screen name="DrawerCart" component={isTablet ? DrawerCart : MainStack} />
</Drawer.Navigator>
);
}
// Drawer 2
import CartScreen from '#features/cart/CartScreen';
import { createDrawerNavigator } from '#react-navigation/drawer';
import * as React from 'react';
import MainStack from './MainStack';
const Drawer = createDrawerNavigator();
export default function DrawerCart(props) {
return (
<Drawer.Navigator
initialRouteName="MainStackCart"
drawerContent={() => <CartScreen />}
drawerPosition='right'
>
<Drawer.Screen name="MainStackCart" component={MainStack} />
</Drawer.Navigator>
);
}
// set navigation of drawer 1 with navigationDrawerHome
let navigationDrawerHome = null
export const setNavigationDrawerHome = (navigation) => {
navigationDrawerHome = navigation
}
export const getNavigationDrawerHome = () => {
return navigationDrawerHome
}
And when I use in Mainstack I when open Drawer 1 in left I use navigationDrawerHome, with drawer 2 just use navigation props
const Stack = createStackNavigator();
function MainStack() {
const navigationDrawerHome = getNavigationDrawerHome()
return (
<Stack.Navigator
screenOptions={({ navigation, route }) => ({
headerLeft: () => <HeaderLeft />,
headerTitleAlign: 'center',
})}>
<Stack.Screen
name="home_user_tab"
component={HomeUserTabs}
options={({ navigation, route }) => ({
headerLeft: () => {
return (
<ButtonIcon
onPress={() => navigationDrawerHome.openDrawer()}
style={styles.buttonLeft}
icon={images.ic_left_menu}
/>
)
},
headerTitle: () => <LogoHeader />,
headerRight: () => (<HeaderCardSearch
navigation={navigation}
/>),
// header: null
})}
/>
</Stack.Navigator>
);
}
const HeaderCardSearch = (props) => {
const { navigation } = props;
return (
<View style={styles.headerRight}>
<ButtonIcon
onPress={() => navigation.openDrawer()}
style={styles.buttonCart}
icon={images.ic_cart}
/>
<ButtonIcon
onPress={() => navigate('search')}
style={styles.buttonSearch}
icon={images.ic_search}
/>
</View>
)
};
I managed to programmatically open both drawers this way:
// Opens left drawer
this.props.navigation.openDrawer();
// Opens right drawer
this.props.navigation.dangerouslyGetParent().dangerouslyGetParent().openDrawer();
And my navigation container looks like this:
static container = (): NavigationContainer => createDrawerNavigator({
right: createDrawerNavigator({
left: DeviceControlContainer
}, {
contentComponent: HistoryDrawerContainer,
overlayColor: drawerOverlayColor()
})
}, {
drawerPosition: 'right',
overlayColor: drawerOverlayColor(),
navigationOptions: DeviceControlScreen.navigationOptions,
contentComponent: DeviceControlDrawer
});
Got inspired from Anh Devit solution, here is mine. I'm using a header bar across the app without a Stack Navigator, so had to go a slightly different route.
For my RootNavigator, I put my Header above my first drawer. I use useState so when I pass down the navigation objects as props, the AppHeader can re-render correctly when the Drawers have been initialized.
import React, { useState } from "react";
import { NavigationContainer } from "#react-navigation/native";
import LeftDrawer from "./LeftDrawer";
import AppHeader from "./AppHeader";
const RootNavigator = () => {
const [navigationLeftDrawer, setNavigationLeftDrawer] = useState(null);
const [navigationRightDrawer, setNavigationRightDrawer] = useState(null);
return (
<NavigationContainer>
<AppHeader
navigationLeftDrawer={navigationLeftDrawer}
navigationRightDrawer={navigationRightDrawer}
/>
<LeftDrawer
navigationLeftDrawer={(navigation) =>
setNavigationLeftDrawer(navigation)
}
navigationRightDrawer={(navigation) =>
setNavigationRightDrawer(navigation)
}
/>
</NavigationContainer>
);
};
export default RootNavigator;
Here I'm creating my LeftDrawer, in which the RightDrawer will be nested. Notice setting the navigationLeftDrawer when the Drawer inits. Also, navigationRightDrawerhas to be passed down as a param and not a prop.
import React, { useContext } from "react";
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItem,
} from "#react-navigation/drawer";
import RightDrawer from "./RightDrawer";
const LeftDrawer = ({ navigationLeftDrawer, navigationRightDrawer }) => {
const Drawer = createDrawerNavigator();
return (
<Drawer.Navigator
screenOptions={({ navigation }) => {
navigationLeftDrawer(navigation);
}}
drawerContent={(props) => {
const { navigate } = props.navigation;
return (<RightDrawerItems />)
);
}}
>
<Drawer.Screen
name="AccountDrawer"
component={RightDrawer}
initialParams={{ navigationRightDrawer }}
/>
</Drawer.Navigator>
);
export default LeftDrawer;
Likewise in RightDrawer, passing up navigationRightDrawer with the right navigation object.
import React from "react";
const RightDrawer = ({ route }) => {
const Drawer = createDrawerNavigator();
const { navigationRightDrawer } = route.params;
return (
<Drawer.Navigator
drawerPosition="right"
screenOptions={({ navigation }) => {
navigationRightDrawer(navigation);
}}
drawerContent={(props) => {
const { navigate } = props.navigation;
return ( <RightDrawerItems />);
}}
>
{userAuth ? (
<>
<Drawer.Screen name={routes.dashboard} component={DashboardScreen} />
</>
) : (
<Drawer.Screen name={routes.home} component={HomeStack} />
)}
<Drawer.Screen name={routes.about} component={AboutScreen} />
<Drawer.Screen name={routes.language} component={LanguageScreen} />
</Drawer.Navigator>
);
};
export default RightDrawer;
Finally, the AppHeader :
import React, { useContext } from "react";
const AppHeader = ({ navigationRightDrawer, navigationLeftDrawer }) => {
if (navigationRightDrawer == null || navigationLeftDrawer == null)
return null;
return (
<View style={styles.container}>
<Icon
name="menu"
color={colors.white}
onPress={() => {
navigationRightDrawer.closeDrawer();
navigationLeftDrawer.toggleDrawer();
}}
/>
{userAuth ? (
<TouchableWithoutFeedback
onPress={() => {
navigationLeftDrawer.closeDrawer();
navigationRightDrawer.toggleDrawer();
}}
>
<Image
style={styles.image}
source={{ uri: userProfile.profilePicture }}
/>
</TouchableWithoutFeedback>
) : (
<Icon
name="person"
color={colors.secondary}
onPress={() => navigationLeftDrawer.navigate(routes.login)}
/>
)}
</View>
);
};
export default AppHeader;

Categories

Resources