An Introduction to React Native Maps in Expo with TypeScript
Tags: reactnative, expo, reactnativemaps, react
Published: 2020-09-06 03:01:17

## Introduction

[Expo](https://docs.expo.io/) is a fast an easy way to get started creating mobile apps with React Native, and all without needing MacOS to test on your iPhone. Used in conjunction with `react-native-maps`, you can have an interactive Map up and running on a phone in no time.

## Getting Started with Expo
First, install Expo globally with `npm i expo -g`. Then, use `expo init` to create an Expo project! Name your project, and then select "tabs (TypeScript)" under Managed workflow. Name your app. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bdt5a69bgto4d1dvtmmm.png)

Then run `expo init`, install the Expo app on your mobile device, open camera, and scan the QR code you see in the terminal or web browser that opens up. You should see the template load up with two clickable tabs at the bottom.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/91m03g0z3bsahemyhlaq.png)

## Your First Map
Now run `expo install react-native-maps`. This will install the latest `react-native-maps` along with any dependencies for the current SDK (38.0 at the time of writing this blog post).

Now replace TabOnScreen.tsx with the following:
```typescript
// TabOnScreen.tsx
import React, { useState } from 'react';
import { StyleSheet, Dimensions } from 'react-native';

import { View } from '../components/Themed';
import MapView from 'react-native-maps';

const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 29.9990674;
const LONGITUDE = -90.0852767;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;

export default function TabOneScreen({ provider }) {
  const [region, setRegion] = useState({
    latitude: LATITUDE,    // initial location latitude
    longitude: LONGITUDE,  // initial location longitude
    latitudeDelta: LATITUDE_DELTA,
    longitudeDelta: LONGITUDE_DELTA,
  });
  return (
    <View style={styles.container}>
      <MapView
        provider={provider}
        style={styles.map}
        initialRegion={region}
        zoomTapEnabled={false}
      ></MapView>
    </View>
  );
}

const styles = StyleSheet.create({
  map: {
    ...StyleSheet.absoluteFillObject,
  },
});
```
![Maps on iOS](https://dev-to-uploads.s3.amazonaws.com/i/s0sr2lnst6v90k8v2rcq.PNG)
Here we created a simple `MapView` component from `react-native-maps`.


## Maps with Markers and Custom Callouts
Now for an example with multiple different types of InfoWindows that pop up on Markers with information referring to that specific marker. In `react-native-maps`, these are referred to as Callouts. 

![Map with Markers](https://dev-to-uploads.s3.amazonaws.com/i/d050cqmz6j0mdvhpqbxu.PNG)
This will demonstrate three different types of Callouts that we can use with our Markers. Here, we have buttons to show and hide the Callout for the last selected Marker, and an overlay with just some explanatory text as well.

First, create new file called `CustomCallouts.ts` and place the code from [here](https://github.com/react-native-community/react-native-maps/blob/master/example/examples/CustomCallout.js) into it.

Then, in `TabTwoScreen.tsx`, place the following code below:
```typescript
// TabTwoScreen.tsx
import React, { useState } from 'react';
import { StyleSheet, Dimensions, TouchableOpacity, Alert } from 'react-native';

import { Text, View } from '../components/Themed';
import MapView, { Marker, Callout, CalloutSubview } from 'react-native-maps';
import CustomCallout from './CustomCallout';

const { width, height } = Dimensions.get('window');
const ASPECT_RATIO = width / height;
const LATITUDE = 29.9990674;
const LONGITUDE = -90.0852767;
const LATITUDE_DELTA = 0.0922;
const LONGITUDE_DELTA = LATITUDE_DELTA * ASPECT_RATIO;
const SPACE = 0.01;

export default function TabTwoScreen({provider}) {
  const [count, setCount] = useState(0);
  const [region, setRegion] = useState({
    latitude: LATITUDE,
    longitude: LONGITUDE,
    latitudeDelta: LATITUDE_DELTA,
    longitudeDelta: LONGITUDE_DELTA,
  });
  const [markers, setMarkers] = useState([
    {
      coordinate: {
        latitude: LATITUDE + SPACE,
        longitude: LONGITUDE + SPACE,
      },
    },
    {
      coordinate: {
        latitude: LATITUDE + SPACE,
        longitude: LONGITUDE - SPACE,
      },
    },
    {
      coordinate: {
        latitude: LATITUDE,
        longitude: LONGITUDE,
      },
    },
    {
      coordinate: {
        latitude: LATITUDE,
        longitude: LONGITUDE - SPACE / 2,
      },
    },
  ]);

  const [markerRefs, setMarkerRefs] = useState([
    {
      ref: null,
    },
    {
      ref: null,
    },
    {
      ref: null,
    },
    {
      ref: null,
    },
  ]);

  const show = () => {
    markerRefs[0].ref.showCallout();
  };

  const hide = () => {
    markerRefs[0].ref.showCallout();
  };

  return (
    <View style={styles.container}>
      <MapView
        provider={provider}
        style={styles.map}
        initialRegion={region}
        zoomTapEnabled={false}
      >
        <Marker
          ref={(ref) => {
            let updateRef = markerRefs;
            updateRef[0].ref = ref;
            setMarkerRefs(updateRef);
          }}
          coordinate={markers[0].coordinate}
          title="This is a native view"
          description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation"
        />
        <Marker coordinate={markers[1].coordinate}>
          <Callout style={styles.plainView}>
            <View>
              <Text>This is a plain view</Text>
            </View>
          </Callout>
        </Marker>
        <Marker
          coordinate={markers[2].coordinate}
          calloutOffset={{ x: -8, y: 28 }}
          calloutAnchor={{ x: 0.5, y: 0.4 }}
          ref={(ref) => {
            let updateRef = markerRefs;
            updateRef[1].ref = ref;
            setMarkerRefs(updateRef);
          }}
        >
          <Callout
            alphaHitTest
            tooltip
            onPress={(e) => {
              if (
                e.nativeEvent.action === 'marker-inside-overlay-press' ||
                e.nativeEvent.action === 'callout-inside-press'
              ) {
                return;
              }

              Alert.alert('callout pressed');
            }}
            style={styles.customView}
          >
            <CustomCallout>
              <Text>{`This is a custom callout bubble view ${count}`}</Text>
              <CalloutSubview
                onPress={() => {
                  setCount(count + 1);
                }}
                style={[styles.calloutButton]}
              >
                <Text>Click me</Text>
              </CalloutSubview>
            </CustomCallout>
          </Callout>
        </Marker>
        <Marker
          ref={(ref) => {
            let updateRef = markerRefs;
            updateRef[3].ref = ref;
            setMarkerRefs(updateRef);
          }}
          coordinate={markers[3].coordinate}
          title="You can also open this callout"
          description="by pressing on transparent area of custom callout"
        />
      </MapView>
      <View style={styles.buttonContainer}>
        <View style={styles.bubble}>
          <Text>Tap on markers to see different callouts</Text>
        </View>
      </View>
      <View style={styles.buttonContainer}>
        <TouchableOpacity
          onPress={() => show()}
          style={[styles.bubble, styles.button]}
        >
          <Text>Show</Text>
        </TouchableOpacity>
        <TouchableOpacity
          onPress={() => hide()}
          style={[styles.bubble, styles.button]}
        >
          <Text>Hide</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  customView: {
    width: 140,
    height: 140,
  },
  plainView: {
    width: 60,
  },
  container: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'flex-end',
    alignItems: 'center',
  },
  map: {
    ...StyleSheet.absoluteFillObject,
  },
  bubble: {
    flex: 1,
    backgroundColor: 'rgba(255,255,255,0.7)',
    paddingHorizontal: 18,
    paddingVertical: 12,
    borderRadius: 20,
  },
  latlng: {
    width: 200,
    alignItems: 'stretch',
  },
  button: {
    width: 80,
    paddingHorizontal: 12,
    alignItems: 'center',
    marginHorizontal: 10,
  },
  buttonContainer: {
    flexDirection: 'row',
    marginVertical: 20,
    backgroundColor: 'transparent',
  },
  calloutButton: {
    width: 'auto',
    backgroundColor: 'rgba(255,255,255,0.7)',
    paddingHorizontal: 6,
    paddingVertical: 6,
    borderRadius: 12,
    alignItems: 'center',
    marginHorizontal: 10,
    marginVertical: 10,
  },
});
```

![One Marker selected with a Title and Description](https://dev-to-uploads.s3.amazonaws.com/i/ydfc20gufftihxom7060.PNG)
The first kind of Callout just has a simple Title and Description.

![Another Marker selected with different formatting and just a description](https://dev-to-uploads.s3.amazonaws.com/i/ewsmz44vhyg7kpby6dty.PNG)
The second Callout uses the plain format, and only has a description, and has a darker pop-up window.

![Marker with clickable button that adds 1 to counter, current counter value 0](https://dev-to-uploads.s3.amazonaws.com/i/ldyv4pi91lo87hpje219.PNG)
Third, we have a CustomCallout with a clickable button, and a default value of `0`. Let's observe what happens on button-click.

![Marker with pressable button that adds 1 to counter, current counter value 1](https://dev-to-uploads.s3.amazonaws.com/i/5pvgccp0yl86f35h8mf7.PNG)
Ah, the magic of `useState`! On press, the counter increments up by 1, and the value is now `1`.

![Marker with clickable button that adds 1 to counter, current counter value 2](https://dev-to-uploads.s3.amazonaws.com/i/f01d66xc2omke1ci3idx.PNG)
And the same thing happens when we press the button again, the total is now `2`.

## Conclusion
For further exploration with Markers in `react-native-maps`, I recommend adding an `image` tag to your Marker, as is done with this [example](https://github.com/react-native-community/react-native-maps/blob/master/example/examples/CustomMarkers.js). `react-native-maps` is super responsive in Expo, and adding maps to your app is an easy way to impress. For further reading, check out the `react-native-maps` [documentation](https://github.com/react-native-community/react-native-maps).  Additionally, for further inspiration, look through the [folder of examples on GitHub](https://github.com/react-native-community/react-native-maps/tree/master/example/examples).