Search code examples
react-nativeredux-toolkitreact-native-mapsreact-navigation-bottom-tab

React native maps error while navigation between screens


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:

  1. If I navigate directly to the Locations tab from the Home screen, the map loads without issues. However, if I switch to any other tab, I receive an error message.
  2. If I navigate from the Home screen to the FAQs or Products tab, and then to the Locations screen, I encounter the same issue again.

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. enter image description here

One odd thing I just found is, though I'm in Locations, the error reads as the previous screen I came from (FAQsScreen) Thanks


Solution

  • 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.

    Make the data a consistent shape

    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>
    

    Handle potentially undefined properties

    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>