React native mapbox-gl crashes with react-navigation - android

I have a problem. When I display a map with #rnmapbox/maps and that I want to navigate in my screens, the app freeze then crashes. I'm pretty sure that this is a rnmapbox problem because when I remove the code between <MapboxGL.MapView> and <MapboxGL.MapView/>, navigation works fine.
I use #react-navigation/native and #react-navigation/native-stack
Note that this issus don't append on iOS, only on Android.
Record of the issue : https://vimeo.com/723749736
Here is my code :
App.js
import React from 'react';
import MapScreen from './src/screens/MapScreen';
import PlaceDetailsScreen from './src/screens/PlaceDetailsScreen';
import VideoPlayerScreen from './src/screens/VideoPlayerScreen';
import {createNativeStackNavigator} from '#react-navigation/native-stack';
import {NavigationContainer} from '#react-navigation/native';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
function App() {
const Stack = createNativeStackNavigator();
return (
<SafeAreaProvider>
<GestureHandlerRootView style={{flex: 1}}>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Map"
component={MapScreen}
options={{headerShown: false}}
/>
<Stack.Group>
<Stack.Screen name="Details" component={PlaceDetailsScreen} />
<Stack.Screen
name="VideoPlayer"
component={VideoPlayerScreen}
options={{headerShown: false}}
/>
</Stack.Group>
</Stack.Navigator>
</NavigationContainer>
</GestureHandlerRootView>
</SafeAreaProvider>
);
}
export default App;
MapScreen.js
import React, {useState, useRef, useMemo, useCallback, useEffect} from 'react';
import {View, Text, PermissionsAndroid, Platform} from 'react-native';
import MapboxGL from '#rnmapbox/maps';
import BottomSheet, {BottomSheetScrollView} from '#gorhom/bottom-sheet';
import Styles from '../../Styles';
import PlaceBtn from '../components/PlaceBtn/PlaceBtn';
import IconPin from './../assets/icons/icon-locationPin.svg';
MapboxGL.setAccessToken(
'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
);
function MapScreen({navigation}) {
const [hasGeolocPermission, setHasGeolocPermission] = useState(false);
const [currentRegion, setCurrentRegion] = useState([4.824, 45.76]); //longitude, latitude
const [userLocation, setUserLocation] = useState();
const [nearestPoints, setNearestPoints] = useState([]);
const markers = require('../data/markers.json');
const route = require('../data/route.json');
const routeLine = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: route.points,
},
},
],
};
const bottomSheetRef = useRef(null);
const snapPoints = useMemo(() => ['13%', '75%', '100%'], []);
const handleSheetChanges = useCallback(index => {
console.log('handleSheetChanges', index);
}, []);
const requestLocationPermission = async () => {
if (Platform.OS === 'android') {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
);
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
console.log('location uses is granted');
setHasGeolocPermission(true);
} else {
console.log(granted);
console.log('location access denied');
}
} catch (error) {
console.warn('error while request location : ', error);
}
}
};
useEffect(() => {
requestLocationPermission();
}, []);
return (
<View style={Styles.container}>
<MapboxGL.MapView style={Styles.map} scaleBarEnabled={false}>
<MapboxGL.UserLocation
animated
showsUserHeadingIndicator
minDisplacement={1}
onUpdate={position => {
console.log('position : ', position);
}}
/>
<MapboxGL.Camera centerCoordinate={currentRegion} zoomLevel={14} />
{markers.map((marker, index) => {
if (marker.category === 'introduction') {
console.log('intro');
} else {
return (
<MapboxGL.PointAnnotation
key={index}
id={marker.id + ''}
coordinate={[marker.longitude, marker.latitude]}>
<IconPin width={35} height={35} fill={'#00c0aa'} />
</MapboxGL.PointAnnotation>
);
}
})}
<MapboxGL.ShapeSource id="line1" shape={routeLine}>
<MapboxGL.LineLayer
id="linelayer1"
style={{lineColor: 'red', lineWidth: 5}}
/>
</MapboxGL.ShapeSource>
</MapboxGL.MapView>
<BottomSheet
ref={bottomSheetRef}
handleIndicatorStyle={Styles.handleIndicator}
index={0}
snapPoints={snapPoints}
onChange={handleSheetChanges}>
<View style={Styles.bottomSheetContent}>
<Text style={Styles.bottomSheetTitle}>{route.name}</Text>
<BottomSheetScrollView>
{markers.map((place, index) => {
return (
<PlaceBtn
place={place}
navigation={navigation}
//isNear={nearestPoints.includes(place.id)}
/>
);
})}
</BottomSheetScrollView>
</View>
</BottomSheet>
</View>
);
}
export default MapScreen;
PlaceDetailsScreen.js
import React from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
function PlaceDetailsScreen({navigation}) {
return (
<>
<TouchableOpacity
style={{backgroundColor: '#00FF00'}}
onPress={() => navigation.navigate('Map')}>
<Text>Go to MAP</Text>
</TouchableOpacity>
<TouchableOpacity
style={{backgroundColor: '#00FF00'}}
onPress={() => navigation.navigate('VideoPlayer')}>
<Text>Go to Video Player</Text>
</TouchableOpacity>
</>
);
}
```

I had the same problem. In my case I have a modal that is persistent through all screens and that modal shrinks when user swipes down like in youtube. After I initialized map inside modal, whenever I try to change screen my app were first freezing then crashing.
My solution was to render map only when modal is expanded. So I made a conditional rendering. Checked whether modal is shrinked or not. If shrinked, then render the map, else don't render. Thus my navigation worked without any problem.
This was my case and that's how I solved it:
// I use reduxjs toolkit.
// Since this modal is gonna be persistent through all screens
// It's best to store translateY value in redux.
const Modal = useAppSelector((state) => state.Modal);
const translateY = useSharedValue(Modal.translateY);
// I watch this variable with an useEffect hook to dynamically update redux value
const [translateYState, setTranslateYState] = useState(translateY.value);
useEffect(() => {
dispatch(setTranslateY(translateYState));
}, [translateYState]);
// translateYState's value is dynamically updating by a pan gesture detector
// translateYState value to be 0 means modal is expanded and user is using the modal
return (
<>
{translateYState==0 && (
<MapboxGL.MapView style={styles.map}></MapboxGL.MapView>
)}
</>
)
You can un-render the map when the modal which has title "Les Sens de Lyon" expands. If you are worried about your map data, you can store the map data for example all locations of markers in redux to be persistent but if your app is a single page app it should not be a problem else you can use redux.

Related

how to display a fetch of react-redux on the Drawer.Screen header

I would like to display data on a Status header bar of Drawer Screen.
My code:
import React, {useEffect, useRef, useState} from 'react';
import {Dimensions, FlatList, Text} from 'react-native';
import {useDispatch, useSelector} from 'react-redux';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {get_agency_direction} from '../store/Log/Dir/DirActions';
import Direction from '../screens/Direction/Direction';
import {createDrawerNavigator} from '#react-navigation/drawer';
const Drawer = createDrawerNavigator();
const RDirection = () => {`enter code here`
const info_agency = useSelector(state => state.DirReducers.info_agency);
const dispatch = useDispatch();
useEffect(() => {
try {
AsyncStorage.getItem('token').then(async value => {
dispatch(get_agency_direction(value));
});
} catch (e) {
console.log(e);
}
});
console.log(info_agency);
return (
<Drawer.Navigator
initialRouteName="Direction"
screenOptions={{
drawerStyle: {
width: Dimensions.get('window').width - 40,
},
}}
drawerType="permanent"
drawerContent={props => <DrawerDirection {...props} />}
backBehavior="history">
<Drawer.Screen
name="Direction"
component={Direction}
options={({navigation}) => ({
header: () => (
<HeaderUser
Gtitle='Noblesse voyages'
Depart="Douala"
Arrive="Yaoundé"
onPress={() => {
navigation.toggleDrawer();
}}
/>
),
headerStyle: {
height: 70,
},
})}
/>
</Drawer.Navigator>
);
};
export default RDirection;
The return code by console.log(info_agency) :
[{
"id":46,
"NomCh":"Bricks",
"PrenomCh":"Wandem foué",
"MotdepasseCh":"$2b$10$/4ugSQMptSp6VqScLcHl4.V9oyrgnAk7NNmtZIeRZ7usT1hcMsbB2",
"NumeroCh":673845359,
"NomAgence":"Talla voyages",
"NomOrganisation":"Talla",
"idch":49,
"CodesecureAgence":"$2b$10$lJ0oBoWr7vnI0Ix.YpWDo.tsiYJ8bSm/yLhVSDf8kgT4CfYFZm7Bm",
"NumeroSerieAg":"",
}]
I would have a truck like that
header: () => (
<HeaderUser
Gtitle={info_agency.NomAgence}
Depart={/* Not important*/}
Arrive={/* Not important*/}
onPress={() => {
navigation.toggleDrawer();
}}
/>
I tried with a Flatlist, but without success.
Your console.log shows that you're response returns an array. You're trying to call info_agency.NomAgence which won't work since it's not an object.
If you want to pass the title into Gtitle you would need to get the object form the array:
info_agency[0].NomAgence

The action 'Navigate' with payload undefined was not handled by any navigator. ( REACT-NATIVE)

I am trying to build an app with 5 screens , this is my code.
++ ADDED APP.JS
// ./App.js
import React from "react";
import { NavigationContainer } from "#react-navigation/native";
import { MainStackNavigator } from "./Screens/StackNavigator";
import DrawerNavigator from "./Screens/DrawerNavigator";
const App = () => {
return (
<NavigationContainer>
<DrawerNavigator />
</NavigationContainer>
);
}
export default App
*HOMESCREEN.JS*
import React from "react";
import { View, Button, Text, StyleSheet,Image } from "react-native";
const Home = ({navigation}) => {
return (
<View style = {styles.firstPage}>
<View style = {styles.topHeader}><Text style={{fontSize:30}}>WORLD GUIDE</Text></View>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Image
style={styles.tinyLogo}
source={require('../images/curvedArrow.png')}
/>
<Text> SLIDE RIGHT TO START EXPLORE !</Text>
</View>
</View>
);
};
*StackNavigator.JS*
import React from "react";
import { createStackNavigator } from "#react-navigation/stack";
import Home from "./HomeScreen";
import Fransa from "./FransaScreen";
import FransaGezi from"./FransaGezi";
const Stack = createStackNavigator();
const MainStackNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Page1-Trav" component={FransaGezi}/>
</Stack.Navigator>
);
}
const FransaStackNavigator = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Fransa" component={Fransa} />
</Stack.Navigator>
);
}
export { MainStackNavigator, FransaStackNavigator};
*FransaScreen.JS*
import React from "react";
import { View, StyleSheet, Text, Image,TouchableOpacity} from "react-native";
import Home from './HomeScreen'
import FransaGezi from './FransaGezi'
const Fransa = ({navigation}) => {
return (
<View style = {styles.firstPage}>
<View style = {styles.sectopHeader}>
<Image
style={styles.bigImage}
source={require('../images/eskis.jpg')}
/>
</View>
<View style = {styles.botHeader}>
<View style= {styles.firstBoxTop}>
<View style = {styles.firstBox}>
<TouchableOpacity onPress={() =>
navigation.navigate(FransaGezi)
}>
<Image source={require('../images/gezi.png')} style = {styles.ImageClass} />
</TouchableOpacity>
</View>
<View style = {styles.secBox}>
<TouchableOpacity>
<Image source={require('../images/food.png')} style = {styles.ImageClass} />
</TouchableOpacity>
</View>
</View>
<View style= {styles.firstBoxBot}>
<View style = {styles.firstBox}>
<TouchableOpacity>
<Image source={require('../images/para.png')} style = {styles.ImageClass} />
</TouchableOpacity>
</View>
<View style = {styles.secBox}>
<TouchableOpacity>
<Image source={require('../images/popmekan.png')} style = {styles.ImageClass} />
</TouchableOpacity>
</View>
</View>
</View>
</View>
);
};
*DrawerNavigator.JS*
import React from "react";
import { createDrawerNavigator } from "#react-navigation/drawer";
import { FransaStackNavigator } from "./StackNavigator";
import Home from "./HomeScreen";
import FransaGezi from "./FransaGezi";
import Fransa from "./FransaScreen";
import { StackActions } from "#react-navigation/native";
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={Home} />
<Drawer.Screen name="Fransa" component={FransaStackNavigator} />
</Drawer.Navigator>
);
}
export default DrawerNavigator;
*FransaGezi.JS*
import React from "react";
import { View, Button, Text, StyleSheet,Image } from "react-native";
const FransaGezi = ({}) => {
return (
<View style = {styles.firstPage}>
<View style = {styles.topHeader}><Text style={{fontSize:30}}>NOLUR ÇALIŞ</Text></View>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Image
style={styles.tinyLogo}
source={require('../images/curvedArrow.png')}
/>
<Text> PLEASE WORK !</Text>
</View>
</View>
);
};
Drawer is working without problem, when I click "Fransa" going related page. But when I click first image in FransaScreen
<TouchableOpacity onPress={() =>
navigation.navigate(FransaGezi)
}>
I get this error message >>>
The action 'Navigate' with payload undefined was not handled by any navigator.
I know that I am missing some part about StackNavigator screen but when I change it like navigation.navigate(Home) it sends me Home page.
Waiting for your helps thanks a lot :)
When dealing with routes in React Native, there are some things you have to put in mind. First of all the route types. In your case you are using StackRoutes, so a basic structure for that would be:
A Routes file
import 'react-native-gesture-handler'
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import { createStackNavigator } from '#react-navigation/stack'
import { Home } from './pages/Home'
import { Dashboard } from './pages/Dashboard'
import { Details } from './pages/Details'
const AppStack = createStackNavigator()
export const Routes = () => {
return (
<NavigationContainer>
<AppStack.Navigator headerMode='none'>
<AppStack.Screen name='Home' component={Home} />
<AppStack.Screen name='Dashboard' component={Dashboard} />
<AppStack.Screen name='Details' component={Details} />
</AppStack.Navigator>
</NavigationContainer>
)
}
In your app, I can see that you have routes with nested routes. In that case you can simply change your component at the AppStack.Screen, and put your routes there. Example:
import DrawerNavigator from 'yout path here'
import FransaGezi from 'your path here too'
// If this is the main Routes component, you should decide what types of navigation you'll use. In this case, let's use a Stack
const AppStack = createStackNavigator()
const Routes = () => {
return(
<NavigationContainer>
<AppStack.Navigator>
<AppStack.Screen name='Some cool name' component={//here you can put a single component or another routes component, such as DrawerNavigator} />
</Appstack.Navigator>
</NavigationContainer>
)
To navigate between routes you can simply do that
//import this hook in a page you want to navigate
import { useNavigation } from '#react-navigation/native'
//you can then use it in your component
const MyComponent = () => {
const navigation = useNavigation()
return (
<Something onClick={() => navigation.navigate('here you need to put the name prop that you provided in your AppStack.Screen, for example, "Some cool name" as specified up in the Routes)} />
)
}
Plus! If I didn't help you, here's a link to React Navigation. Your doubts will surely be answered there :) React Navigation

React Native: Switch from createBottomTabNavigator to createMaterialBottomNavigator

I am fully aware that using createBottomTabNavigator will give you a nice iOS bottom tabs with no background color while createMaterialBottomNavigator will give you a background color and different setup on android device.
My goal is to to use createBottomTabNavigator on iOs device and then switch to createMaterialBottomNavigator on android device with all the setup like putting activeTintColor color, using shifting: true mode whenever you switch screen on android.
So what I did is I use the Platform API:
const MealsFavTabNavigator =
Platform.OS === 'android'
? createMaterialBottomNavigator(navTabConfig)
: createBottomTabNavigator();
And then, create a possible config:
const navTabConfig = {
options={{
tabBarIcon: ({ focused, color, size }) => (
<Ionicons name="ios-star" size={25} color={focused ? "tomato" : "black"} />
)
}}
};
Then tried to switch this on my navigator:
<MealsFavTabNavigator.Screen
name="Meals"
component={MealsNavigator}
navTabConfig
/>
Which did not work. Here's the full code:
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { Ionicons } from '#expo/vector-icons';
import { Platform } from 'react-native';
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs';
import CategoriesScreen from '../screens/CategoriesScreen';
import CategoryMealsScreen from '../screens/CategoryMealsScreen';
import MealDetailScreen from '../screens/MealDetailScreen';
import FavoritesScreen from '../screens/FavoritesScreen';
import HeaderButton from '../components/HeaderButton';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { CATEGORIES } from '../data/dummy-data';
import Colors from '../constants/colors';
const MealsNav = createStackNavigator();
const MealsNavigator = () => {
return (
<MealsNav.Navigator
mode="modal"
screenOptions={{
headerStyle: {
backgroundColor: Colors.primaryColor,
},
headerTintColor: '#fff',
headerTitleStyle: {
fontSize: 17
}
}}
>
<MealsNav.Screen
name="Categories"
component={CategoriesScreen}
options={{
title: 'Meals Categories'
}}
/>
<MealsNav.Screen
name="CategoryMeals"
component={CategoryMealsScreen}
options={({ route }) => {
const catId = route.params.categoryId;
const selectedCategory = CATEGORIES.find((cat) => cat.id === catId);
return {
title: selectedCategory.title,
};
}}
/>
<MealsNav.Screen
name="MealDetail"
component={MealDetailScreen}
options={{
title: 'Meal Detail',
headerRight: () => (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='Favorite'
iconName='ios-star'
onPress={() => console.log('Mark as the favorite')}
/>
</HeaderButtons>
),
}}
/>
</MealsNav.Navigator>
);
};
const MealsFavTabNavigator =
Platform.OS === 'android'
? createMaterialBottomNavigator(navTabConfig)
: createBottomTabNavigator();
const navTabConfig = {
options={{
tabBarIcon: ({ focused, color, size }) => (
<Ionicons name="ios-star" size={25} color={focused ? "tomato" : "black"} />
)
}}
};
const MealsTabNav = () => {
return (
<NavigationContainer>
<MealsFavTabNavigator.Navigator
tabBarOptions={{
activeTintColor: 'tomato',
inactiveTintColor: 'black',
}}>
<MealsFavTabNavigator.Screen
name="Meals"
component={MealsNavigator}
navTabConfig
/>
<MealsFavTabNavigator.Screen
name="Favorites"
component={FavoritesScreen}
options={{
tabBarIcon: ({ focused, color, size }) => (
<Ionicons name="ios-star" size={25} color={focused ? "tomato" : "black"} />
)
}}
/>
</MealsFavTabNavigator.Navigator>
</NavigationContainer>
);
};
export default MealsTabNav;
How can I attain my goal of switching from createBottomTabNavigator to createMaterialBottomNavigator and then applying the basic styles on createMaterialBottomNavigator such as activeTintcolor and shifting mode.
Please help! I am super stuck!
Your approach is correct only little change that you need to do is the way you use the props.
The below code is a generic example, I'm not using your components
The tab creation is same but the when you get the props its better if you can use a function like below.
Here the function will return the props for the appropriate navigator and we use the spread operator to pass the props.
You can chose to do this only tabBarOptions but this example shows for all props which is more flexible.
const Tab = Platform.OS === 'ios' ? createMaterialBottomTabNavigator() : createBottomTabNavigator();
const getNavigationOptions = () => {
if (Platform.OS === 'ios') {
//Props for the ios navigator
return {
labeled: false,
initialRouteName: 'Settings',
activeColor: 'red',
inactiveColor: 'white'
};
}
//Props for any other OS navigator
return {
initialRouteName: 'Home',
tabBarOptions: { activeTintColor: 'red' },
};
};
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator {...getNavigationOptions()}>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
To apply this to your project
//Keep this line as it is
const MealsFavTabNavigator =
Platform.OS === 'android'
? createMaterialBottomNavigator()
: createBottomTabNavigator();
<MealsFavTabNavigator.Navigator {...getNavigationOptions()}>
And update the getNavigationOptions with your own probs based on the platform
Did you try the following?
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs';
const Tab = createMaterialBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}

React Native - Navigate to screen - Invalid hook call

App.js:
import React, { Component } from 'react';
import {View,Text} from 'react-native';
import { createDrawerNavigator } from 'react-navigation-drawer';
import {createAppContainer} from 'react-navigation';
import Epics from './screens/tmp';
import Pager from './screens/Pager';
const DrawerNavigator = createDrawerNavigator({
Home: {screen: Epics},
Page: {screen: Pager}
},{initialRouteName: 'Home'});
const Stack = createAppContainer(DrawerNavigator);
export default class App extends Component {
render() {
return <Stack />;
}
}
Trying to navigate to a screen through a custom component:
import { ActivityIndicator, Image, StyleSheet, View, TouchableHighlight } from 'react-native';
import PropTypes from 'prop-types';
import React from 'react';
import { useNavigation } from 'react-navigation-hooks';
import AuthorRow from './AuthorRow';
export default class Card extends React.Component {
static propTypes = {
fullname: PropTypes.string.isRequired,
image: Image.propTypes.source.isRequired,
linkText: PropTypes.string.isRequired,
onPressLinkText: PropTypes.func.isRequired,
epicId: PropTypes.string.isRequired,
};
state = {
loading: true,
};
getNav(){
return useNavigation();
}
handleLoad = () => {
this.setState({ loading: false });
};
render() {
const { fullname, image, linkText, onPressLinkText, epicId, prop } = this.props;
const { loading } = this.state;
return (
<View>
<AuthorRow
fullname={fullname}
linkText={linkText}
onPressLinkText={onPressLinkText}
/>
<TouchableHighlight onPress={() => this.getNav().navigate('Pager')} underlayColor="white">
<View style={styles.image}>
{loading && (
<ActivityIndicator style={StyleSheet.absoluteFill} size={'large'} />
)}
<Image
style={StyleSheet.absoluteFill}
source={image}
onLoad={this.handleLoad}
/>
</View>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create({
image: {
aspectRatio: 1,
backgroundColor: 'rgba(0,0,0,0.02)',
},
});
From above code, trying to navigate using:
import { useNavigation } from 'react-navigation-hooks';
getNav(){ return useNavigation(); }
this.getNav().navigate('Pager')
It errors our stating: Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component.
How do I navigate using "useNavigation()" or how do I get "this.props.navigation" reference in a component? (I am new to React Native, please help me understand)
Update
I tried using "this.props.navigation" and it gives me undefined error:
#sayog you are breaking the rule of hook brother.
Could you try this?
const [loading, setLoading] = useState(false);
const navigation = useNavigation();
make sure you put it after your state declaration
Note: Dont' use class component for Hook. use Functional Component for hook
//In Card component remove this
//import { useNavigation } from 'react-navigation-hooks';
//Add this line
import {withNavigation} from 'react-navigation';
class Card extends React.Component {
static propTypes = {
fullname: PropTypes.string.isRequired,
image: Image.propTypes.source.isRequired,
linkText: PropTypes.string.isRequired,
onPressLinkText: PropTypes.func.isRequired,
epicId: PropTypes.string.isRequired,
};
state = {
loading: true,
};
//Remove this
// getNav(){
// return useNavigation();
//}
handleLoad = () => {
this.setState({ loading: false });
};
render() {
const { fullname, image, linkText, onPressLinkText, epicId, prop } = this.props;
const { loading } = this.state;
return (
<View>
<AuthorRow
fullname={fullname}
linkText={linkText}
onPressLinkText={onPressLinkText}
/>
<TouchableHighlight onPress={() =>
//Make change like this
this.props.navigation.navigate('Pager')} underlayColor="white">
<View style={styles.image}>
{loading && (
<ActivityIndicator style={StyleSheet.absoluteFill} size={'large'} />
)}
<Image
style={StyleSheet.absoluteFill}
source={image}
onLoad={this.handleLoad}
/>
</View>
</TouchableHighlight>
</View>
);
}
}
const styles = StyleSheet.create({
image: {
aspectRatio: 1,
backgroundColor: 'rgba(0,0,0,0.02)',
},
});
//Make change like this
export default withNavigation(Card)
here is your mistake
mistake
onPress={() => this.getNav().navigate('Pager')
replace with
onPress={() => this.props.navigation.navigate('Pager')
here is the example
https://reactnavigation.org/docs/navigating/
https://medium.com/#marizu_makozi/navigating-between-screens-or-activities-using-react-navigation-library-68d57657d81
Another example code
class FirstScreen extends React.Component {
static navigationOptions = {
title: 'First',
}
componentDidMount(){
const {navigate} = this.props.navigation;
navigate('Second');
fetch('http://apiserver').then( (response) => {
navigate('Second');
});
}
render() {
return (
<AppLogo />
);
}
}
const AppNavigator = StackNavigator({
First: {
screen: FirstScreen,
},
Second: {
screen: SecondScreen,
},
});
You cannot use hooks inside Class component convert your class component to a functional component or use navigation object for navigations by passing navigation props to your component this.props.navigation.navigate('screenName')

this.props.navigation is undefined when using DrawerNavigator

I'm integrating DrawerNavigator of 'react-navigation' to my project as this document. But when I run the project it always get this error when click on the Button:
TypeError: undefined is not an object (evaluating
'this.props.navigation.navigate')
And when I swipe from left to right, nothing happen, no drawer open.
I've check this.props and it's always log empty {} in console.
I tried many solutions but it's still not working.
calculator.js
export default class Calculator extends Component {
constructor(props) {
super(props);
}
static navigationOptions = {
drawerLabel: 'Calculator',
drawerIcon: ({ tintColor }) => (
<Image
source={require('./../../res/images/icon_calculator.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
render() {
return (
<View style={styles.container}>
<View>
<Text style={styles.title}>Tip Calculator</Text>
</View>
<Button
onPress={() => this.props.navigation.navigate("SettingsScreen")}
title="Go to settings"
/>
</View>
);
}
}
module.exports = Calculator;
settings.js
export default class Settings extends Component {
constructor(props) {
super(props);
}
static navigationOptions = {
drawerLabel: 'Settings',
drawerIcon: ({ tintColor }) => (
<Image
source={require('./../../res/images/icon_settings.png')}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
render() {
return (
<View style={styles.container}>
<Text>Settings</Text>
<Button
onPress={() => this.props.navigation.goBack()}
title="Go back home"
/>
</View>
);
}
}
module.exports = Settings;
navigation.js
import {
DrawerNavigator
} from 'react-navigation';
import Calculator from './../components/calculator/calculator.js';
import Settings from './../components/settings/settings.js';
const RootDrawer = DrawerNavigator({
CalculatorScreen: {
path: '/',
screen: Calculator
},
SettingsScreen: {
path: '/sent',
screen: Settings
}
}, {
initialRouteName: 'CalculatorScreen',
drawerPosition: 'left'
});
export default RootDrawer;
App.js
export default class App extends Component<{}> {
render() {
return (
<Calculator/>
);
}
}
index.js
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('rn_tip_calculator', () => App);
Do I have to use StackNavigator with DrawerNavigator, or am I missed something in config?
Full source code, it's only a simple example project, please have a look: https://github.com/HCMUS-IceTeaViet-SE/rn_tip_calculator
Any help will be appreciated. Thanks!
You can use dispatch api https://reactnavigation.org/docs/navigators/navigation-actions
1) import navigation actions
import { NavigationActions } from 'react-navigation'
2) dispatch navigation action:
const navigateAction = NavigationActions.navigate({
routeName: 'SettingsScreen',
params: {},
})
this.props.navigation.dispatch(navigateAction)
I'm pretty new to React-Native but sometimes this also happens to me.
I'm using Redux and stack navigator.... but here is my working example...
import { StackNavigator } from 'react-navigation'
import { Animated, Easing } from 'react-native'
import LoginScreen from '../Containers/LoginScreen'
import LaunchScreen from '../Containers/LaunchScreen'
import HomeScreen from '../Containers/HomeScreen'
import SignUpScreen from '../Containers/SignUpScreen'
import SettingsScreen from '../Containers/SettingsScreen'
import VehicleCreateScreen from '../Containers/VehicleCreateScreen'
import styles from './Styles/NavigationStyles'
// Manifest of possible screens
const PrimaryNav = StackNavigator({
LoginScreen: { screen: LoginScreen },
LaunchScreen: { screen: LaunchScreen },
HomeScreen: { screen: HomeScreen },
SignUpScreen: { screen: SignUpScreen },
SettingsScreen: { screen: SettingsScreen },
VehicleCreateScreen: { screen: VehicleCreateScreen }
}, {
// Default config for all screens
headerMode: 'none',
initialRouteName: 'LaunchScreen',
navigationOptions: {
headerStyle: styles.header
},
transitionSpec: {
duration: 0,
timing: Animated.timing,
easing: Easing.step0,
},
},
)
export default PrimaryNav
And then from a component not connected to REDUX
import React, { Component } from 'react';
import { Container, Content, List, ListItem, Icon, Text, Button, Left, Right, Badge } from 'native-base';
import { Image } from 'react-native'
import styles from './Styles/SideBarStyle';
// import backgroundImage from '../Images/vw.jpg'
const backgroundImage = require("../Images/vw.jpg");
const drawerImage = require("../Images/dirtyHandsDark.jpg");
export default class SideBar extends Component {
constructor(props) {
super(props);
}
render() {
// *********** HERE WE DECLARE AN ARRAY TO RENDER LISTS FROM. THIS COULD ALSO BE LIST OF BIKES FROM STORE.. ***********
const datas = [
{
name: "Home",
route: "HomeScreen",
icon: "settings",
bg: "#C5F442",
},
{
name: "Repair",
route: "HomeScreen",
icon: "settings",
bg: "#C5F442",
},
{
name: "My Profile",
route: "SettingsScreen",
icon: "settings",
bg: "#C5F442",
},
];
return (
<Container>
<Content bounces={false} style={{ flex: 1, backgroundColor: "#fff", top: -1 }}>
<Image source={backgroundImage} style={styles.drawerCover}>
<Image square style={styles.drawerImage} source={drawerImage} />
</Image>
<List
dataArray={datas}
renderRow={data =>
// *********** CREATE NEW LIST ITEM ON CLICK NAVIGATE TO APPROPRIATE LISTITEM.SCREEN ***********
<ListItem button noBorder onPress={() => this.props.navigation.navigate(data.route)}>
<Left>
<Icon active name={data.icon} style={{ color: "#777", fontSize: 26, width: 30 }} />
<Text style={styles.text}>
{data.name}
</Text>
</Left>
{data.types &&
<Right style={{ flex: 1 }}>
<Badge
style={{
borderRadius: 3,
height: 25,
width: 72,
backgroundColor: data.bg,
}}
>
<Text style={styles.badgeText}>{`${data.types} Types`}</Text>
</Badge>
</Right>}
</ListItem>}
/>
</Content>
</Container>
);
}
}
You can see I reference this.props.navigation.navigate no problem.
Heres my repo for reference.
https://github.com/GavinThomas1192/motoMechanicMeeKanic/tree/master/App
This happen because I don't "connect" my DrawerNavigator to my app.
There is 2 ways to achieve this:
First way: register DrawerNavigator as app root component. In index.js change:
from AppRegistry.registerComponent('rn_tip_calculator', () => App);
to AppRegistry.registerComponent('rn_tip_calculator', () => RootDrawer);
So you could delete App.js because it's useless now.
Second way: Keep register App component (in App.js) as app root component. Then put inside App component to "connect" DrawerNavigator to the app.
export default class App extends Component<{}> {
render() {
return (
<RootDrawer/>
);
}
}
The document say nothing about how to connect the navigator to the app, nothing about register component or put navigator inside root component. This drive newbie like me mad!

Categories

Resources