I am currently building a React Native application that comprises four different screens Home
, Products
, FAQs
, and Locations
. The HomeScreen
is a static screen with no content displayed. The Products
, FAQs
, and Locations
screens all retrieve their data from a data file named data.js
. The Locations screen includes the react-native-maps
component, which I am using to display a map. Currently, I am displaying the names of Products and FAQs on their respective screens, which are retrieved from the data file.
I have encountered a challenge with the Locations screen. If I only display the names of locations, everything works fine. However, when I attempt to render the Map component, an error occurs under the following scenarios:
However, when I switch between the FAQs and Products tabs, there are no errors, regardless of how many times I do it.
Here is my code for the Locations Screen and my Navigation.
LocationsScreen.js
import { useDispatch, useSelector } from 'react-redux';
import { Text, View, StyleSheet } from 'react-native';
import { useIsFocused } from '@react-navigation/native';
import {
setResults_disp,
setClearResults_disp,
} from '../features/SearchbarSlice';
import { useEffect, useRef } from 'react';
import data from '../../data/data.json';
import MapView, { Callout, Marker } from 'react-native-maps';
const LocationsScreen = () => {
const focus = useIsFocused();
const dispatch = useDispatch();
const { results, isLoading_disp } = useSelector(
(state) => state.searchReducer
);
const mapRef = useRef(null);
useEffect(() => {
if (focus) {
dispatch(setClearResults_disp());
dispatch(setResults_disp(data[0].locations));
}
}, [focus]);
return (
<>
{isLoading_disp && !results.length >= 1 ? (
<View>
<Text>Loading</Text>
</View>
) : (
<View style={{ flex: 1 }}>
{results && (
<MapView style={styles.map}>
{results.map((data, index) => (
<Marker
key={index}
coordinate={{
latitude: data.latitude,
longitude: data.longitude,
}}
pinColor="#ab7a5f">
<Callout>
<Text>{data.name}</Text>
</Callout>
</Marker>
))}
</MapView>
)}
</View>
)}
</>
);
};
export default LocationsScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
flex: 1,
},
});
Navigation.js
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { FontAwesome5 } from '@expo/vector-icons';
import HomeScreen from './Screens/HomeScreen';
import ProductsScreen from './Screens/ProductsScreen';
import FAQsScreen from './Screens/FAQsScreen';
import LocationsScreen from './Screens/LocationsScreen';
const Tab = createBottomTabNavigator();
const Navigation = () => {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: () => {
let iconName;
if (route.name === 'Home') {
iconName = 'home';
} else if (route.name === 'Products') {
iconName = 'shopping-bag';
} else if (route.name === 'FAQs') {
iconName = 'book';
} else if (route.name === 'Locations') {
iconName = 'map';
}
return <FontAwesome5 name={iconName} size={24} color="black" />;
},
headerShown: false,
})}>
<Tab.Screen
name="Home"
component={HomeScreen}
screenOptions={{ headerShrown: false }}
/>
<Tab.Screen
name="Products"
component={ProductsScreen}
initialParams={{ verticalKey: 'products' }}
/>
<Tab.Screen
name="FAQs"
component={FAQsScreen}
initialParams={{ verticalKey: 'faqs' }}
/>
<Tab.Screen
name="Locations"
component={LocationsScreen}
initialParams={{ verticalKey: 'locations' }}
/>
</Tab.Navigator>
</NavigationContainer>
);
};
export default Navigation;
And here is a working snack to fiddle with. Please let me know where I'm going wrong and how can I fix this.
Here is a screenshot of the error I get.
One odd thing I just found is, though I'm in Locations
, the error reads as the previous screen I came from (FAQsScreen
)
Thanks
The issue is either inconsistent data shape, e.g. the location data is missing a rawData
property, or the UI code needs to be more resilient and handle potentially undefined data.
Move the location data into a rawData
property.
data.json
[
{
"locations": [
{
"rawData": {
"name": "New York City",
"latitude": 40.7128,
"longitude": -74.0060,
"state": "New York"
}
},
{
"rawData": {
"name": "Los Angeles",
"latitude": 34.0522,
"longitude": -118.2437,
"state": "California"
}
},
{
"rawData": {
"name": "Chicago",
"latitude": 41.8781,
"longitude": -87.6298,
"state": "Illinois"
}
},
{
"rawData": {
"name": "Houston",
"latitude": 29.7604,
"longitude": -95.3698,
"state": "Texas"
}
},
{
"rawData": {
"name": "Miami",
"latitude": 25.7617,
"longitude": -80.1918,
"state": "Florida"
}
}
]
},
{
"products": [
{
"rawData": {
"id": "Product - 1",
"name": "iPhone 9",
"price": 549,
"description": "An apple mobile which is nothing like apple"
}
},
{
"rawData": {
"id": "Product - 2",
"name": "iPhone X",
"price": 899,
"description": "SIM-Free, Model A19211 6.5-inch Super Retina HD display with OLED technology A12 Bionic chip with ..."
}
},
{
"rawData": {
"id": "Product - 3",
"name": "Samsung Universe 9",
"price": 1249,
"description": "Samsung's new variant which goes beyond Galaxy to the Universe"
}
},
{
"rawData": {
"id": "Product - 4",
"name": "OPPOF19",
"price": 280,
"description": "OPPO F19 is officially announced on April 2021."
}
}
]
},
{
"FAQs": [
{
"rawData": {
"id": "FAQ - 1",
"name": "Question 1",
"description": "Answer 1"
}
},
{
"rawData": {
"id": "FAQ - 2",
"name": "Question 2",
"description": "Answer 2"
}
},
{
"rawData": {
"id": "FAQ - 3",
"name": "Question 3",
"description": "Answer 3"
}
},
{
"rawData": {
"id": "FAQ - 4",
"name": "Question 4",
"description": "Answer 4"
}
}
]
}
]
LocationsScreen.jsx
<View style={{ flex: 1 }}>
{results && (
<MapView style={styles.map}>
{results.map((data, index) => (
<Marker
key={index}
coordinate={{
latitude: data.rawData.latitude,
longitude: data.rawData.longitude,
}}
pinColor="#ab7a5f">
<Callout>
<Text>{data.rawData.name}</Text>
</Callout>
</Marker>
))}
</MapView>
)}
</View>
Provide fallback values in the other screens where item.rawData
is potentially undefined.
ProductsScreen.jsx
<View style={styles.container}>
{results.map((item, index) => (
<Text key={index}>{item.rawData?.name ?? ''}</Text>
))}
</View>
FAQsScreen.jsx
<View style={styles.container}>
{results.map((item, index) => (
<Text key={index}>{item.rawData?.name ?? ''}</Text>
))}
</View>
or filter the data prior to mapping.
<View style={styles.container}>
{results
.filter(item => item.rawData)
.map((item, index) => (
<Text key={index}>{item.rawData.name}</Text>
)
)}
</View>