I am using react-navigation 5 in my mobile app and I am stuck in implementing a log-off feature.
The scenario is I have a stack navigator and a bottom tab navigator in my app. The app starts with the stack navigator (Login feature and Reset Password Feature) and on login goes to the dashboard Page which is from the bottom tab navigator. Now on the Dashboard page, I am implementing a logout feature which when clicked should take me to the login page (part of stack navigator), and no matter what I try it keeps giving me errors like these
The action 'RESET' with payload {"index":0,"routes":[{"name":"AuthNavigator"}]} was not handled by any navigator.
Here are my code snippets right from start
Component Called from App.js
import React, { useState, useEffect, useContext } from "react";
import { ActivityIndicator } from "react-native";
import AsyncStorage from '#react-native-async-storage/async-storage';
import { Center } from "../../components/Center";
import { AuthContext } from "../authentication/AuthProvider";
import { NavigationContainer } from "#react-navigation/native";
import { AuthNavigator } from "./AuthNavigator";
import { MainTabNavigator } from "./MainTabNavigator";
export default function App() {
const { user, login } = useContext(AuthContext);
const [loading, setLoading] = useState(true);
useEffect(() => {
// check if the user is logged in or not
//AsyncStorage.removeItem("user") //- uncomment this and refresh emulator to start from login screen
AsyncStorage.getItem("user")
.then(userString => {
if (userString) {
login();
}
setLoading(false);
})
.catch(err => {
console.log(err);
});
}, []);
if (loading) {
return (
<Center>
<ActivityIndicator size="large" />
</Center>
);
}
return (
<NavigationContainer>
{user ? <MainTabNavigator /> : <AuthNavigator />}
</NavigationContainer>
);
}
AuthNavigator.js
import React from "react";
import { createStackNavigator } from '#react-navigation/stack';
import Login from '../authentication/Login';
import ResetPassword from '../authentication/ResetPassword';
import { MainTabNavigator } from "./MainTabNavigator";
const Stack = createStackNavigator();
export const AuthNavigator = () => {
return (
<Stack.Navigator initialRouteName="Login">
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="ResetPassword" options={{headerTitle: "Reset Password"}} component={ResetPassword} />
</Stack.Navigator>
);
}
MainTabNavigator.js
import * as React from 'react';
import { Text, View, Image, StyleSheet, Platform } from 'react-native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import DashboardView from '../dashboard/DashboardView';
import Search from '../searchLoan/Search';
import { colors } from '../../styles';
const iconHome = require('../../../assets/images/tabbar/home.png');
const iconGrids = require('../../../assets/images/tabbar/grids.png');
const searchIcon = require('../../../assets/images/pages/search_24px.png');
const Tab = createBottomTabNavigator();
const tabData = [
{
name: 'Dashboard',
component: DashboardView,
icon: iconHome,
},
{
name: 'Search',
component: Search,
icon: searchIcon,
},
];
export const MainTabNavigator = () => {
return (
<Tab.Navigator tabBarOptions={{ style: { height: Platform.OS === 'ios' ? 90 : 50 } }}>
{tabData.map((item, idx) => (
<Tab.Screen
key={`tab_item${idx + 1}`}
name={item.name}
component={item.component}
options={{
tabBarIcon: ({ focused }) => (
<View style={styles.tabBarItemContainer}>
<Image
resizeMode="contain"
source={item.icon}
style={[styles.tabBarIcon, focused && styles.tabBarIconFocused]}
/>
</View>
),
tabBarLabel: ({ focused }) => <Text style={{ fontSize: 12, color: focused ? colors.primary : colors.gray }}>{item.name}</Text>,
title: item.name,
}}
/>
))}
</Tab.Navigator>
);
};
const styles = StyleSheet.create({
tabBarItemContainer: {
alignItems: 'center',
justifyContent: 'center',
borderBottomWidth: 2,
borderBottomColor: colors.white,
paddingHorizontal: 10,
bottom: Platform.OS === 'ios' ? -5 : 0,
},
tabBarIcon: {
width: 23,
height: 23,
},
tabBarIconFocused: {
tintColor: colors.primary,
},
});
DashboardView.js
import React , {useContext }from 'react';
import { StyleSheet, View, TouchableOpacity, Text } from 'react-native';
import {Header} from 'react-native-elements';
import AntIcon from "react-native-vector-icons/AntDesign";
import { colors, fonts } from '../../styles';
import AmountDetails from './AmountDetails';
import DashboardFields from './DashboardFields';
import { AuthContext } from "../authentication/AuthProvider";
import { CommonActions } from "#react-navigation/native";
export default function DashboardView(props) {
const appLogOut = () => {
const { logout } = useContext(AuthContext);
console.log('props', props)
if(logout){
// console.log("Navigation Object", navigation)
props.navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: "AuthNavigator" }],
}));
}
}
return (
<View style={styles.container}>
<Header containerStyle = {{backgroundColor: colors.primary}}
centerComponent={{ text: 'Dashboard', style: { color: colors.white, backgroundColor: colors.primary } }}
rightComponent = <TouchableOpacity onPress={appLogOut()}><AntIcon name="logout" color="white" size={25}/></TouchableOpacity>
/>
<View style={styles.container}>
<View>
<AmountDetails />
</View>
<View style={styles.dashboardFields}>
<DashboardFields />
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.gray,
},
dashboardFields: {
marginTop: 20,
},
});
You should try calling the login screen directly, not the whole stack.
CommonActions.reset({
index: 0,
routes: [{ name: "Login" }],
}));
As the other answer said, you have incorrect route name (AuthNavigator).
However, you're conditionally defining screens based on if the user is logged in. You don't need to do anything extra when logging out. Conditionally defining screens means React Navigation can automatically handle which screen to show when the conditional changes.
So you need to remove the code which does reset.
From the docs:
It's important to note that when using such a setup, you don't need to manually navigate to the Home screen by calling navigation.navigate('Home') or any other method. React Navigation will automatically navigate to the correct screen when isSigned in changes - Home screen when isSignedIn becomes true, and to SignIn screen when isSignedIn becomes false. You'll get an error if you attempt to navigate manually.
More details: https://reactnavigation.org/docs/auth-flow/
Related
Well, I made a presentation onboarding screen, as soon as the user opens the app, the screen is shown, but I want this to be saved using AsyncStorage, if I close and open the app, the onboarding screen is not shown, but the screen of login.
I did all the code but nothing happens and the screen is displayed every time I close and open the app, I don't know what I'm doing wrong, code below.
App.js
import React, { useEffect, useState } from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import {AuthProvider} from './src/providers/Auth';
import AsyncStorage from '#react-native-async-storage/async-storage';
import { OnBoarding } from './src/screen/OnBoarding';
import { SignIn } from './src/screen/SignIn';
import { HomeScreen } from './src/screen/HomeScreen';
import { ActivityIndicator } from 'react-native';
const Stack = createNativeStackNavigator();
const Loading = () => {
return (
<View>
<ActivityIndicator size="large" />
</View>
);
}
export function App(){
const [loading, setLoading] = useState(true);
const [viewedOnboarding, setViewedOnboarding] = useState(false);
useEffect(() => {
checkOnBoarding();
}, [])
const checkOnBoarding = async () => {
try{
const value = await AsyncStorage.getItem('#viewedOnboarding');
if(value !== null){
setViewedOnboarding(true);
}
console.log(value);
}catch(err) {
console.log('Error #checkOnboarding: ', err);
}finally {
setLoading(false)
}
}
return (
<AuthProvider>
<NavigationContainer>
<Stack.Navigator
initialRouteName={loading ? <Loading /> : viewedOnboarding ? <HomeScreen /> : <OnBoarding />}
screenOptions={
{headerShown: false}
}
>
<Stack.Screen
name="SignIn"
component={SignIn}
/>
</Stack.Navigator>
</NavigationContainer>
</AuthProvider>
);
}
Onboarding.js
import React, { useEffect, useState } from 'react';
import {Text, View, StyleSheet, Image, TouchableOpacity, Button} from 'react-native';
import AppIntroSlider from 'react-native-app-intro-slider';
import Icon from 'react-native-vector-icons/FontAwesome';
import AsyncStorage from '#react-native-async-storage/async-storage';
const slides = [
{
key: 1,
title: 'Only Books Can Help You',
text: 'Books can help you to increase your knowledge and become more successfully.',
image: require('../../assets/imagem1.png'),
},
{
key: 2,
title: 'Learn Smartly',
text: 'It’s 2022 and it’s time to learn every quickly and smartly. All books are storage in cloud and you can access all of them from your laptop or PC.',
image: require('../../assets/imagem2.png'),
},
{
key: 3,
title: 'Learn Smartly',
text: 'It’s 2022 and it’s time to learn every quickly and smartly. All books are storage in cloud and you can access all of them from your laptop or PC.',
image: require('../../assets/imagem2.png'),
},
];
export function OnBoarding(){
function renderItem({item}){
return (
<View style={styles.container}>
<Image style={styles.image} source={item.image} />
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.content}>{item.text}</Text>
</View>
);
}
function _renderPrevButton() {
return (
<View style={styles.buttonCircle}>
<Icon
name="angle-left"
color="#000"
size={40}
/>
</View>
);
};
function _renderNextButton() {
return (
<View style={styles.buttonCircle}>
<Icon
name="angle-right"
color="#000"
size={40}
/>
</View>
);
};
const onPressFinish = async () => {
try{
await AsyncStorage.setItem('#viewedOnboarding', 'true')
navigation.navigate('SignIn');
}catch(err) {
console.log("Error #setitem ", err);
}
};
const renderDoneButton = () => {
return (
<TouchableOpacity onPress={onPressFinish}>
<Text style={{color: "#000"}}>Done</Text>
</TouchableOpacity>
);
};
return (
<AppIntroSlider
data={slides}
renderItem={renderItem}
keyExtractor={item => item.key}
renderPrevButton={_renderPrevButton}
renderNextButton={_renderNextButton}
renderDoneButton={renderDoneButton}
showPrevButton
showDoneButton
dotStyle={{backgroundColor: '#9D9D9D'}}
activeDotStyle={{backgroundColor: '#DE7773'}}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: "#fff"
},
title: {
color: "#292B38",
fontSize: 24,
fontWeight: 'bold',
marginTop: 50
},
content: {
color: "#4D506C",
textAlign: 'center',
padding: 25,
lineHeight: 18
},
image: {
width: 300,
height: 300,
},
button: {
color: "#000",
backgroundColor: "transparent"
}
})
Probably because of the async nature of AsyncStore when you open your app first you init viewedOnboarding with false, then start reading storage and then you check is onboarding done or not. And of course because storage is async first time you check in Navigation condition you always get
I have modified your checkOnBoarding function.
Here is the code:
const checkOnBoarding = () => {
try{
AsyncStorage.getItem('#viewedOnboarding').then(value => {
if(value !== null){
setViewedOnboarding(true);
}
console.log(value);
})
}catch(err) {
console.log('Error #checkOnboarding: ', err);
}finally {
setLoading(false)
}
}
My idea is to have nav.js contain the navigation container for my mobile application, along with all of the screens here (i.e. the homescreen). This file would theoretically let me include buttons in another file that would let me navigate to the screen in the nav.js file that I specified with the button.
I made a constant for this called NavBar and exported it, but when I tried to import into my photo.js file (an example file where I want to include a button that lets me navigate to my 'Tasks' screen), I got the error:
Error: Looks like you have nested a 'NavigationContainer' inside another. Normally you need only one container at the root of the app, so this was probably an error. If this was intentional, pass 'independent={true}' explicitly. Note that this will make the child navigators disconnected from the parent and you won't be able to navigate between them.
I tried simply making my own button in photo.js instead of :
<Button title="Go to tasks" color="lightblue" onPress={() => navigation.navigate('Tasks')}/>, but that only gave me the error that the variable navigation could not be found.
nav.js
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { StyleSheet, View, Button, ScrollView } from 'react-native';
import TaskApp from './taskapp';
import AddTank from './tank';
const NavBar = () => {
const Stack = createNativeStackNavigator();
{/* Homescreen */}
const HomeScreen = ( {navigation} ) => {
return (
<ScrollView style={homeStyle.container}>
<AddTank />
<Button title="Go to tasks" color="lightblue" onPress={() => navigation.navigate('Tasks')}/>
</ScrollView>
)
}
{/* Default config */}
const Config = ( {navigation } ) => {
return (
<View></View>
)
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name="Home" component={HomeScreen}/>
<Stack.Screen name="Tasks" component={TaskApp}/>
<Stack.Screen name="Config" component={Config}/>
</Stack.Navigator>
</NavigationContainer>
)
}
const homeStyle = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#E8EAED',
},
tasks: {
flex: 1,
height: 30,
width: 30,
backgroundColor: '#E8EAED',
},
});
export default NavBar;
photo.js
import React, {useState, useEffect} from 'react';
import { StyleSheet, Text, View, Button, Image, ImageBackground } from 'react-native';
import { NavigationContainer, StackActions } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import * as ImagePicker from 'expo-image-picker';
import NavBar from './nav';
const AddPhoto = () => {
const add = {uri: 'https://media.discordapp.net/attachments/639282516997701652/976293252082839582/plus.png?width=461&height=461'}
const Stack = createNativeStackNavigator();
const [hasGalleryPermission, setHasGalleryPermission] = useState(null);
const[ image, setImage ] = useState(null);
useEffect(() => {
(async () => {
const galleryStatus = await ImagePicker.requestCameraMediaLibraryPermissionAsync();
setHasGalleryPermission(galleryStatus.status === 'granted');
})();
}, []);
const pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4,3],
quality:1,
});
console.log(result);
if (!result.cancelled){
setImage(result.uri);
}
};
if (hasGalleryPermission === false){
return <Text>No access to internal storage.</Text>
}
return (
<View style={styles.container}>
{image && <ImageBackground source={{uri: image}} style={styles.tankPhoto}/>}
<Button title="Add photo" color="lightgreen" onPress={() => pickImage()} />
<NavBar />
</View>
)
}
const styles = StyleSheet.create({
container: {
borderColor: '#C0C0C0',
borderWidth: 1,
backgroundColor: '#FFF',
borderRadius: 50,
width: 330,
flex: 1,
alignItems: 'center',
marginLeft: 'auto',
marginRight: 'auto',
margin: 30,
},
tankPhoto: {
flex: 1,
height: 150,
width: 200,
borderWidth: 1,
margin: 25,
},
})
export default AddPhoto;
What am I doing wrong, or is there a better way to do this? What I need:
let me include buttons in another file that would let me navigate to the screen in the nav.js file that I specified with the button.
React native navigation documentation: https://reactnavigation.org/docs/getting-started
Move these lines outside the NavBar component:
const Stack = createNativeStackNavigator();
{/* Homescreen */}
const HomeScreen = ( {navigation} ) => {
return (
<ScrollView style={homeStyle.container}>
<AddTank />
<Button title="Go to tasks" color="lightblue" onPress={() => navigation.navigate('Tasks')}/>
</ScrollView>
)
}
{/* Default config */}
const Config = ( {navigation } ) => {
return (
<View></View>
)
}
I ended up making this work. I defined
const navigation = useNavigation();
in my code (under AddPhoto constant) and imported:
import { useNavigation } from '#react-navigation/native'; in the same file (photo.js).
How to add submenu or tabs in the upper part in connection with the lower button and show the name of the lower button in the header of the application.
import React from 'react';
import { createStackNavigator } from 'react-navigation-stack';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import { createAppContainer } from 'react-navigation';
import { MaterialIcons, MaterialCommunityIcons } from '#expo/vector-icons';
import Splash from '../screens/Splash';
import NewsListScreen from '../screens/NewsListScreen';
import NewsItemScreen from '../screens/NewsItemScreen';
const StackNavigator = createStackNavigator({
//Splash: {screen: Splash},
News: {
screen: NewsListScreen
},
NewsItem: {
screen: NewsItemScreen,
navigationOptions: {
headerTitle: 'News Item'
}
}
});
const BottomTabNavigator = createBottomTabNavigator({
Home: {
screen: StackNavigator,
navigationOptions: {
tabBarIcon: () => <MaterialIcons name="home" size={24} />
}
},
News: {
screen: StackNavigator,
navigationOptions: {
tabBarIcon: () => <MaterialCommunityIcons name="newspaper-variant-outline" size={24} />
}
}
})
export default createAppContainer(BottomTabNavigator);
What I want to achieve is the following:
As you can see, the bottom button [News] has three referential buttons in the upper part [FEATURED], [RELEVANT], [SEARCH] and, in addition to that, it recovers the name of the bottom button and adds it to the application header below the top buttons.
[EDITED]
Your NewsListScreen screen, instead of just being a tab bar component, can be something like that:
function NewsListScreen = (props) => (
<View>
<Text>News</Text>
<TabBarComponent {...props} />
</View>
)
function TabBarComponent = createMaterialTopTabNavigator({
featured: ... ,
relevant: ... ,
search: ... ,
})
That being said, if you can, you should update your project to react navigation v5, which is I think much more user friendly.
Final Output:
Implementation using react-navigation v5 :
import * as React from 'react';
import { Text, View, StyleSheet, Dimensions } from 'react-native';
import Constants from 'expo-constants';
import {
NavigationContainer,
useNavigationState,
} from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createMaterialTopTabNavigator } from '#react-navigation/material-top-tabs';
const TopTab = createMaterialTopTabNavigator();
const BottomTab = createBottomTabNavigator();
/*
Here I am using MaterialTopTabNavigator instead of StackNavigator,
which is a better choice based on UI snapshot in this case.
*/
function NewsTopTabs() {
/*
in case you want to set the title dynamically then you can use the
useNavigationState hook but as there are just two bottom tabs so
I am using hardcoded values.
*/
const nav = useNavigationState((state) => state);
return (
<>
<View style={styles.titleBar}>
<Text style={styles.title}>News</Text>
</View>
<TopTab.Navigator>
<TopTab.Screen name="Featured" component={Featured} />
<TopTab.Screen name="Relevant" component={Relevant} />
<TopTab.Screen name="Search" component={Search} />
</TopTab.Navigator>
</>
);
}
function MatchesTopTabs() {
return (
<>
<View style={styles.titleBar}>
<Text style={styles.title}>Matches</Text>
</View>
<TopTab.Navigator>
<TopTab.Screen name="Featured" component={Featured} />
<TopTab.Screen name="Relevant" component={Relevant} />
<TopTab.Screen name="Search" component={Search} />
</TopTab.Navigator>
</>
);
}
function MyBottomTabs({ navigation }) {
return (
<BottomTab.Navigator>
<BottomTab.Screen name="News" component={NewsTopTabs} />
<BottomTab.Screen name="Matches" component={MatchesTopTabs} />
</BottomTab.Navigator>
);
}
//[FEATURED], [RELEVANT], [SEARCH]
export default function App() {
return (
<NavigationContainer>
<MyBottomTabs />
</NavigationContainer>
);
}
//[FEATURED], [RELEVANT], [SEARCH]
const Featured = () => {
const nav = useNavigationState((state) => state);
return (
<View style={styles.screen}>
<Text>Featured</Text>
</View>
);
};
const Relevant = () => {
const nav = useNavigationState((state) => state);
return (
<View style={styles.screen}>
<Text>Relevant</Text>
</View>
);
};
const Search = () => {
const nav = useNavigationState((state) => state);
return (
<View style={styles.screen}>
<Text>Search</Text>
</View>
);
};
const styles = StyleSheet.create({
titleBar: {
height: 50,
width: Math.round(Dimensions.get('window').width),
backgroundColor: 'white',
justifyContent: 'center',
borderBottomColor: "lightgrey",
borderBottomWidth: 1,
paddingLeft: 10,
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
screen:{
flex: 1,
justifyContent: "center",
alignItems: "center"
}
});
Working Example: Expo Snack
I have created the side menu using DrawerNavigator with the static menu names and screens. I want the menu names and screens to be dynamic. Meaning to say I like to fetch those details in API and display in the DrawerNavigator. Please help to resolve this.
const MyApp = createDrawerNavigator({
HomeScreen: {
screen: HomeScreen,
},
WebMenuScreen: {
screen: WebMenuScreen,
},
}, {
drawerPosition: 'right',
},{
contentComponent: CustomDrawerComponent
}, {
initialRouteName: 'Login'
});
Try this, I hope this will helps you.
in Route File
import DrawerNavigator from "./DrawerNavigator";
import Home from './Home';
import About from './About'
import {
createStackNavigator,
createAppContainer,
} from "react-navigation";
const MainNavigator = createStackNavigator({
DrawerNavigator: {
screen: DrawerNavigator,
navigationOptions: {
header: null
}
},
Home: {
screen: Home,
navigationOptions: {
htitle: 'Home'
}
},
About: {
screen: About,
navigationOptions: {
htitle: 'About'
}
},
});
const Routes = createAppContainer(MainNavigator);
export default Routes;
in DrawerNavigator File
import React from 'react';
import { Platform, Dimensions } from 'react-native';
import { createDrawerNavigator, createAppContainer } from 'react-navigation';
import Home from './Home';
import About from '/About'
import MenuDrawer from './MenuDrawer';
const WIDTH = Dimensions.get('window').width;
const DrawerConfig = {
drawerWidth: WIDTH*0.83,
contentComponent: ({ navigation }) => {
return(<MenuDrawer navigation={navigation} />)
}
}
const DrawerNavigator = createDrawerNavigator(
{
Home:{
screen:Home
},
About:{
screen:About
},
},
DrawerConfig
);
export default createAppContainer(DrawerNavigator);
in Menu Drawer File
import React from "react";
import {
View,
Text,
ScrollView,
Image,
TouchableOpacity,
} from "react-native";
var obj = JSON.parse('{ "name":"Home","name":"About" }');
// import styles from './menuDrawerStyles'
class MenuDrawer extends React.Component {
navLink(nav, text) {
return (
<TouchableOpacity
style={{ height: 50 }}
onPress={() => this.props.navigation.navigate(nav)}
>
<Text style={styles.link}>{text}</Text>
</TouchableOpacity>
);
}
render() {
return (
<View style={styles.container}>
<ScrollView style={styles.scroller}>
{
this.obj.map((data) => {
<View style={styles.bottomLinks}>
<View style={{ flex: 2, flexDirection: "row" }}>
{this.navLink(data.name, data.name)}
</View>
<View style={{ flex: 2, flexDirection: "row" }}>
{this.navLink(data.name, data.name)}
</View>
</View>
})
}
</ScrollView>
</View>
);
}
}
in Menu Button File
import React from "react";
import { StyleSheet , Platform } from "react-native";
import Icon from "react-native-vector-icons/MaterialIcons";
export default class MenuButton extends React.Component {
render() {
return (
<Icon
name="reorder"
color="black"
size={25}
style={styles.menuIcon}
onPress={() => this.props.navigation.toggleDrawer()}
/>
);
}
}
const styles = StyleSheet.create({
menuIcon: {
zIndex: 9,
position: "absolute",
top: Platform.OS === "android" ? 15 : 25,
left: 20
}
});
In Menu Drawer file you can call Api and fetch menu list.
I'm getting the error:
Invariant Violation: withNavigation can only be used on a view
hierarchy of a navigator. The wrapped component is unable to get
access to navigation from props or context
I don't know why, because I'm using withNavigation in other components in my app and it works. I don't see a difference in the components that it works on to the one that causes the error.
Code:
the component:
const mapStateToProps = (state: State): Object => ({
alertModal: state.formControls.alertModal
})
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => {
return bindActionCreators(
{
updateAlertModalHeight: updateAlertModalHeight,
updateAlertModalIsOpen: updateAlertModalIsOpen,
updateHasYesNo: updateAlertModalHasYesNo
},
dispatch
)
}
class AlertModalView extends Component<AlertModalProps, State> {
render(): Node {
return (
<View style={alertModalStyle.container}>
<PresentationalModal
style={presentationalModalStyle}
isOpen={this.props.alertModal.isOpen}
title={this.props.alertModal.title}
navigation={this.props.navigation}
updateHasYesNo={this.props.updateHasYesNo}
message={this.props.alertModal.message}
updateAlertModalHeight={this.props.updateAlertModalHeight}
viewHeight={this.props.alertModal.viewHeight}
hasYesNo={this.props.alertModal.hasYesNo}
yesClicked={this.props.alertModal.yesClicked}
updateAlertModalIsOpen={this.props.updateAlertModalIsOpen}
/>
</View>
)
}
}
// $FlowFixMe
const AlertModalViewComponent = connect(
mapStateToProps,
mapDispatchToProps
)(AlertModalView)
export default withNavigation(AlertModalViewComponent)
the stackNavigator:
import React from 'react'
import { View, SafeAreaView } from 'react-native'
import Icon from 'react-native-vector-icons/EvilIcons'
import Add from '../product/add/view'
import Login from '../user/login/view'
import Search from '../product/search/query/view'
import { Image } from 'react-native'
import { StackNavigator, DrawerNavigator, DrawerItems } from 'react-navigation'
const AddMenuIcon = ({ navigate }) => (
<View>
<Icon
name="plus"
size={30}
color="#FFF"
onPress={() => navigate('DrawerOpen')}
/>
</View>
)
const SearchMenuIcon = ({ navigate }) => (
<Icon
name="search"
size={30}
color="#FFF"
onPress={() => navigate('DrawerOpen')}
/>
)
const Stack = {
Login: {
screen: Login
},
Search: {
screen: Search
},
Add: {
screen: Add
}
}
const DrawerRoutes = {
Login: {
name: 'Login',
screen: Login
},
'Search Vegan': {
name: 'Search',
screen: StackNavigator(Stack.Search, {
headerMode: 'none'
}),
navigationOptions: ({ navigation }) => ({
drawerIcon: SearchMenuIcon(navigation)
})
},
'Add vegan': {
name: 'Add',
screen: StackNavigator(Stack.Add, {
headerMode: 'none'
}),
navigationOptions: ({ navigation }) => ({
drawerIcon: AddMenuIcon(navigation)
})
}
}
const CustomDrawerContentComponent = props => (
<SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}>
<View>
<Image
style={{
marginLeft: 20,
marginBottom: 0,
marginTop: 0,
width: 100,
height: 100,
resizeMode: 'contain'
}}
square
source={require('../../images/logo_v_white.png')}
/>
</View>
<DrawerItems {...props} />
</SafeAreaView>
)
const Menu = StackNavigator(
{
Drawer: {
name: 'Drawer',
screen: DrawerNavigator(DrawerRoutes, {
initialRouteName: 'Login',
drawerPosition: 'left',
contentComponent: CustomDrawerContentComponent,
contentOptions: {
activeTintColor: '#27a562',
inactiveTintColor: 'white',
activeBackgroundColor: '#3a3a3a'
}
})
}
},
{
headerMode: 'none',
initialRouteName: 'Drawer'
}
)
export default Menu
Here I render the StackNavigator which is Menu in my app component:
import React, { Component } from 'react'
import Menu from './menu/view'
import Props from 'prop-types'
import { Container } from 'native-base'
import { updateAlertModalIsOpen } from './formControls/alertModal/action'
import AlertModalComponent from './formControls/alertModal/view'
import UserLoginModal from './user/login/loginModal/view'
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => {})
store.dispatch(this.props.fetchUserGeoCoords())
store.dispatch(this.props.fetchSearchQueryPageCategories())
store.dispatch(this.props.fetchCategories())
}
componentWillUnmount() {
this.unsubscribe()
}
render(): Object {
return (
<Container>
<Menu store={this.context} />
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
<UserLoginModal />
</Container>
)
}
}
Vepo.contextTypes = {
store: Props.object
}
export default Vepo
and my root component:
export const store = createStore(
rootReducer,
vepo,
composeWithDevTools(applyMiddleware(createEpicMiddleware(rootEpic)))
)
import NavigationService from './navigationService'
export const App = () => (
<Provider store={store}>
<Vepo
fetchUserGeoCoords={fetchUserGeoCoords}
fetchSearchQueryPageCategories={fetchSearchQueryPageCategories}
fetchCategories={fetchCategories}
/>
</Provider>
)
AppRegistry.registerComponent('vepo', () => App)
I have changed my Vepo component to this to implement the answer by vahissan:
import React, { Component } from 'react'
import Menu from './menu/view'
import Props from 'prop-types'
import { Container } from 'native-base'
import { updateAlertModalIsOpen } from './formControls/alertModal/action'
import AlertModalComponent from './formControls/alertModal/view'
import UserLoginModal from './user/login/loginModal/view'
import NavigationService from './navigationService'
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => {})
store.dispatch(this.props.fetchUserGeoCoords())
store.dispatch(this.props.fetchSearchQueryPageCategories())
store.dispatch(this.props.fetchCategories())
}
componentWillUnmount() {
this.unsubscribe()
}
render(): Object {
return (
<Container>
<Menu
store={this.context}
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef)
}}>
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
</Menu>
<UserLoginModal />
</Container>
)
}
}
Vepo.contextTypes = {
store: Props.object
}
export default Vepo
No errors, but the alertModal no longer displays
In react-navigation, the main StackNavigator creates a context provider and the navigation prop will be made available to any component below its level in the component tree if they use the context consumer.
Two ways to access the navigation prop using context consumer is to either add the component to the StackNavigator, or use the withNavigation function. However because of how React's context API works, any component that uses withNavigation function must be below the StackNavigator in the component tree.
If you still want to access the navigation prop regardless of the position in component tree, you will have to store the ref to the StackNavigator in a module. Following guide from react-navigation will help you do that https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
If you are using react-navigation version 5, use useNavigation hook with a functional component. This hook injects the navigation object into the functional component. Here is the link to the docs:
https://reactnavigation.org/docs/use-navigation/
Vahissan's answer is correct but I could not get it working because of various differences in my code from https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html like my stackNavigator not being a component, it is just an object.
What I managed to do was get the AlertModal component to be a child of the stackNavigator and thus receive the navigation prop by adding it to my StackNavigator's ContentComponent. The code is as above but int the CustomDrawerContentComponent just making it like this:
const CustomDrawerContentComponent = props => (
<SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}>
<View>
<Image
style={{
marginLeft: 20,
marginBottom: 0,
marginTop: 0,
width: 100,
height: 100,
resizeMode: 'contain'
}}
square
source={require('../../images/logo_v_white.png')}
/>
</View>
<DrawerItems {...props} />
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
</SafeAreaView>
)
For anyone who might be interested, you need to npm install #react-navigation/native #react-navigation/compat #react-navigation/stack for withNavigation to work in the recent version