1

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

user3872094
  • 3,269
  • 8
  • 33
  • 71

1 Answers1

1

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>
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • Hey Drew. Thanks for this. It worked perfectly. Now when I updated my data file to look like my JSON response. It is throwing error again, saying `undefined is not an object (evaluating 'data.address.latitude')`. Can you please take a look at it? I've updated the snack. – user3872094 Mar 28 '23 at 21:13
  • @user3872094 It's because `data.address` is undefined when the stored data is something *other than* previously stored location data. Using the Optional Chaining operator is a bandaid fix, e.g. `data.address?.latitude`, but if your data really is just structurally ***and*** objectively ***different*** then perhaps it should occupy its own part of state so you aren't playing whack-a-mole with data structures. – Drew Reese Mar 28 '23 at 22:13