Day 12: Showing user location on embedded Google Maps (with Geolocation API and React)
Tags: geolocation, react, googlemaps, webdev
Published: 2021-10-26 06:54:35

## TL;DR

To create a web app that shows the user's location on embedded Google Maps with React: 

1. Create a state variable that stores an instance of Google Maps, and pass this state to a button component as its prop (Section 1).
2. Once the button is clicked, use Geolocation API to retrieve location data from the user's device, and execute the `setCenter()` method of Google Maps JavaScript API to snap the map to the user's location (Section 2).
3. To mark the user's location on the map, use `google.maps.Marker()` method of Google Maps JavaScript API (Section 3).
4. To show the range of location data error, use `google.maps.Circle()` method to draw a circle whose radius is set in meters (Section 4).
5. To handle Geolocation API errors, update the UI state for each error case (Section 5.3).

In doing so, we need to use React's `useRef` hook to retain the marker for the user's location across the re-rendering of React components, a lesser-known technique of making a React app (Section 3.2).

## Introduction

Showing the user's location on the map is an important feature of _My Ideal Map App_, a web app that I'm building to improve the user experiences of Google Maps. It allows the user to discover which of their saved places (e.g., cafes that they always wanted to go) are close enough to visit now (see [**Day 1 of this blog series**](https://dev.to/masakudamatsu/creating-saas-to-solve-my-own-problem-4gk9) for detail). 

Unlike Google Maps iOS/Android app, however, a web app cannot (and should not try to) show the user's location immediately after the user accesses the app (see [**Day 11 of this blog series**](https://dev.to/masakudamatsu/day-11-learning-about-web-app-limitations-on-location-services-3gdp) for detail). 

The second best option is therefore to show the user's location only after the user taps a button on the screen. 

How to implement such a feature is well-described in the code snippet provided by [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/geolocation). But it is for vanilla JavaScript. I'm using React (Next.js, to be more exact) to build _My Ideal Map App_. And I've gone through a handful of sticking points due to how React works.

For those of you who also create a React app with embedded Google Maps, let me share with you what I have learned to show the user's location on the map.  

### Demo

This article will create an app like [this demo](https://4aa36604.mima.pages.dev/) hosted on [Cloudflare Pages](https://pages.cloudflare.com/). Maybe you want to check it out before reading the rest of this article.

## 1. Setting up

Let me first quickly go through how to embed Google Maps and to render a button over it. 

Write the component for the index page (or  `pages/index.js`  in Next.js) as follows:

```jsx
// pages/index.js

import LocatorButton from '../components/LocatorButton';
import Map from '../components/Map';

function HomePage() {
  const [mapObject, setMapObject] = useState(null);
  return (
    <>
      <LocatorButton mapObject={mapObject} />
      <Map setMapObject={setMapObject} />
    </>
  );
}

export default HomePage;

```

The `mapObject` state variable will store an instance of the embedded Google Maps. The `<Map>` component will embed Google Maps, pass it to `pages/index.js` by executing the `setMapObject()` method. Then the `pages/index.js` will hand it over to the `<LocatorButton>` which will mark the user's current location on the embedded Google Maps.

The `<Map>` component embeds Google Maps with the following code (if the code below is perplexing, see my blog post (**[Kudamatsu 2021](https://dev.to/masakudamatsu/3-gotchas-of-google-maps-api-when-used-with-next-js-and-eslint-p7e)**) in which I explain how to embed Google Maps with Next.js): 

```jsx
// components/Map.js

import {useEffect, useRef} from 'react';
import {Loader} from '@googlemaps/js-api-loader';
import PropTypes from 'prop-types';

const Map = ({setMapObject}) => {
  // Specifying HTML element to which Google Maps will be embeded 
  const googlemap = useRef(null);
  useEffect(() => {
    // Loading Google Maps JavaScript API
    const loader = new Loader({
      apiKey: process.env.NEXT_PUBLIC_API_KEY,
      version: 'weekly',
    });
    let map; 
    loader.load().then(() => {
      // Setting parameters for embedding Google Maps
      const initialView = {
        center: {
          lat: 34.9988127,
          lng: 135.7674863,
        },
        zoom: 14, 
      };
      const buttonsDisabled = {
        fullscreenControl: false,
        mapTypeControl: false,
        streetViewControl: false,
        zoomControl: false,
      };
      // Embedding Google Maps
      const google = window.google;
      map = new google.maps.Map(googlemap.current, {
        ...initialView,
        ...buttonsDisabled,
      });
      setMapObject(map); // NOTE
    });
  }, [setMapObject]);

  return <div ref={googlemap} />;
};

Map.propTypes = {
  setMapObject: PropTypes.func.isRequired,
};

export default Map;
```

What's important for this article is the line commented with `"NOTE"`:

```javascript
setMapObject(map);
```

This passes the embedded Google Maps as a JavaScript object up to the `pages/index.js`.

This way, the `<LocatorButton>` component can access to the embedded Google Maps as its `mapObject` prop:

```jsx
// components/LocatorButton.js

import PropTypes from 'prop-types';

const LocatorButton = ({mapObject}) => {
  return (
    <button
      type="button"
    >
      <!-- Insert the button label image -->
    </button>
  );
};

LocatorButton.propTypes = {
  mapObject: PropTypes.object.isRequired,
};

export default LocatorButton;
```

where I use `PropTypes` to define the type of the `mapObject` prop (see [React documentation](https://reactjs.org/docs/typechecking-with-proptypes.html) for detail on `PropTypes`). 

Now we're ready to mark the user's current location on the embedded Google Maps.

---

Footnote: I use a state variable to pass `mapObject` from `Map` component to `LocatorButton` component. The use of a state variable, however, causes the re-rendering of the entire app once `mapObject` changes from its initial value of `null` to an instance of Google Maps. This is unnecessary re-rendering, because no part of the UI changes after the map is loaded. It's something I need to investigate in the future.

## 2. Snapping map to user location

Showing the user's location on a map means two things: (1) marking the location on the map and (2) snapping the map to it. Let me first tackle the second “snapping” part, because it is relatively simple. 

Let's start by adding a click handler to the `<button>` element:

```jsx
const LocatorButton = ({mapObject}) => {
  const getUserLocation = () => { // ADDED
    // To be defined below        // ADDED
  };                              // ADDED
  return (
    <button
      onClick={getUserLocation}   // ADDED
      type="button"
    >
      <!-- Insert the button label image -->
    </button>
  );
};
```

This is the standard way of adding an event hander in React (see [React documentation](https://reactjs.org/docs/handling-events.html)).

Then we define the `getUserLocation()` function as follows.

First up, handle those legacy browsers that do not support [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API), a web API that allows the browser to access to the location data in the user's device. Following the suggestion by [Kinlan (2019)](https://developers.google.com/web/fundamentals/native-hardware/user-location/#browser_support), I use the feature detection technique to handle those browsers:

```javascript
const getUserLocation = () => {
  if (navigator.geolocation) {
    // code for showing the user's location
  } else {
    // code for legacy browsers
  }
};
```

In Section 5.3 below, I'll briefly discuss how to handle those legacy browsers.

Then, for those browsers that do support Geolocation API, I retrieve the user's current location data from their device by calling the `getCurrentPosition()` method:

```javascript
  const getUserLocation = () => {
    if (navigator.geolocation) {
      // ADDED FROM HERE 
      navigator.geolocation.getCurrentPosition(position => {
        // code for processing user location data           
      });
      // ADDED UNTIL HERE
    } else {
      // code for legacy browsers
    }
  };
```

It's a bit tricky to understand how the `getCurrentPosition()` method works. Here's my understanding (see [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API/Using_the_Geolocation_API#getting_the_current_position) for more proper explanation). 

When it runs, it retrieves the user location data from their device. This is done *asynchronously*: it won't prevent the rest of the code from running immediately after. Once the location data is obtained, it's passed to a function specified as the argument for `getCurrentPosition()`. In the above code, this data is given the name of `position`. Taking `position` as an argument, this function will be executed.

The user location data takes the form of a JavaScript object formally called the [`GeolocationPosition` interface](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPosition), which has a property called `coords`. This `coords` property in turn stores the user's location coordinates as its own `latitude` and `longitude` properties. 

So I store the coordinates of the user's location as a JavaScript object called `userLocation`:

```javascript
  const getUserLocation = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(position => {
        const userLocation = {            // ADDED
          lat: position.coords.latitude,  // ADDED
          lng: position.coords.longitude, // ADDED
        };                                // ADDED
      });
    } else {
      // code for legacy browsers
    }
  };
```

I use property names `lat` and `lng` because that's how Google Maps JavaScript API refers to the coordinates of locations (known as [`LatLng` class](https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLng)).

Now we're ready to use the `setCenter()` method from Google Maps JavaScript API to snap the map to the user's current location:

```javascript
  const getUserLocation = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(position => {
        const userLocation = {
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        };
        mapObject.setCenter(userLocation); // ADDED
      });
    } else {
      // code for legacy browsers
    }
  };
```

where `mapObject`, if you remember, refers to the embedded Google Maps, passed as a prop to the `LocatorButton` component (see Section 1 above if your memory slips).

## 3. Marking user's current location

Now it's time to mark the user's location on the map. 

### 3.1 Marker

As a marker, I imitate what Google Maps app does: a white-rimmed circle in Google's brand blue:

![White-rimmed blue circle shown on Google Maps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bwd6d00ra6vv1ya1vsc.jpeg)

**A screenshot of Google Maps app in which the blue dot indicates the user's current location** (image source: [Google Maps Help](https://support.google.com/maps/thread/71969187/my-google-map-cannot-show-direction-direction-pointer-on-blue-dot-is-missing-what-can-i-do?hl=en))

I've learned about how to render this particular type of the blue dot from [the source code of Geolocation Marker](https://github.com/ChadKillingsworth/geolocation-marker/blob/master/src/geolocation-marker.js):

```javascript
const blueDot = {
  fillColor: color['google-blue 100'],
  fillOpacity: 1,
  path: google.maps.SymbolPath.CIRCLE,
  scale: 8,
  strokeColor: color['white 100'],
  strokeWeight: 2,
}; 
```

where I define the `color` object as design tokens in a separate file:

```javascript
// designtokens.js

export const color = {
  'google-blue 100': `#4285F4`,
  'white 100': `rgb(255,255,255)`,
}
```

I prefer this way of setting color because the color code itself doesn't tell me anything about the reason behind the color choice. For example, the color code `#4285F4` is the blue used in Google's logo (source: [U.S. Brand Colors](https://usbrandcolors.com/google-colors/)). So I call it `google-blue 100` where `100` refers to the opacity of 1. (If I need to use semi-transparent Google Blue, I can then call it `google-blue 50`, for example.)

### 3.2 Adding marker to map

With Google Maps JavaScript API, we can add a marker to the map as follows. First, create a marker as a JavaScript object with the `google.maps.Marker()` method. Then, add the Marker object to the map with its own method `setMap()`.

Sounds simple. But it actually isn't, because I'm using React to build the app.

NOTE: If you only want to know the code that works, skip to the sub-section entitled "Fourth Attempt" below.

**First Attempt**

My first attempt didn't work properly. I created a Marker object:

```javascript
// Don't code like this
const marker = new google.maps.Marker({ 
  icon: blueDot,
  position: userLocation,
  title: 'You are here!'
})
```

where the `icon` property refers to the marker icon (which I have defined as `blueCircle`), `position` to the coordinates of the user's current position (which I have defined as `userLocation`), and `title` to the text to be shown when the user hovers over the marker. (See [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions) for all the options available for the Marker object.)

Then, I added the Marker object to the embedded map: 

```javascript
// Don't code like this
const marker = new google.maps.Marker({ 
  icon: blueDot,
  position: userLocation,
  title: 'You are here!'
});
marker.setMap(mapObject); // ADDED
```

where the `mapObject` refers to the embedded Google Maps, passed as a prop to the `LocatorButton` component (as explained in Section 1 above).

This code caused a problem when the user taps the locator button again. In this situation, the above code adds a new marker at the current location *without* removing the marker at the previous location.

Which means we first need to _remove_ the outdated marker before adding the updated one. To do so, we need to use the Marker object's method `setMap(null)`. Without running this, we would be adding more and more markers to the map.

**Second Attempt**

My second attempt was as follows (which turned out to be not desirable): I checked whether we have already created the Marker object. If so, I'd remove the marker from the map:

```javascript
// Don't code like this
let marker;
if (marker) {
  marker.setMap(null);
}
```

 Then, I created a new marker tied to the user's current position:

```javascript
// Don't code like this
let marker;
if (marker) {
  marker.setMap(null);
}
marker = new google.maps.Marker({ // REVISED
  icon: blueDot,
  position: userLocation,
  title: 'You are here!'
});
marker.setMap(mapObject);
```

This code worked fine, but once I started using the `useState()` hook  inside the `<LocatorButton>` component in order to change the UI in response to user actions (see [**Day 13 of this blog series**](https://dev.to/masakudamatsu/day-13-flashing-tapped-button-while-user-is-waiting-with-react-and-styled-components-1ii4)), the previous marker wasn't removed when the user tapped the button for the second time.

Why? Because using the `useState()` hook causes the re-rendering of the `<LocatorButton>` component, which means the entire code gets re-run, including

```javascript
let marker;
```

This means that every time the component gets re-rendered, the `marker` variable gets reset, losing the data on the previous user location. That's why the previous marker fails to be removed.

**Third Attempt**

My initial work around for this rerendering problem was to define `marker` outside the `<LocatorButton>` component (which worked, but turned out to be not the best practice for building a React app):

```javascript
// This code works, but not the best practice

let marker; // REVISED
const LocatorButton = ({mapObject}) => {
  ...
  if (marker) {
	  marker.setMap(null);
  }
  marker = new google.maps.Marker({
    icon: blueDot,
    position: userLocation,
    title: 'You are here!'
  });
  marker.setMap(mapObject);
  ...
};
```

This way, the `marker` variable will be retained even when the `<LocatorButton>` component gets re-rendered. So the data on the user's previous location won't be lost, and the previous marker will get removed.

But then, while I was working for dealing with another issue (see **Day 14 of this blog series**), I learned about how to use the `useRef()` hook to retain the data across the re-rendering of React components.

Sounds like a solution for removing the previous marker on the user location!

**Fourth Attempt**

So I've revised the code as follows:

```javascript
import {useRef} from 'react';    // ADDED

const LocatorButton = ({mapObject}) => {
  ...
  const marker = useRef(null);   // ADDED
  if (marker.current) {          // REVISED
	  marker.current.setMap(null); // REVISED
  }
  marker.current = new google.maps.Marker({ // REVISED
    icon: blueDot,
    position: userLocation,
    title: 'You are here!'
  });
  marker.current.setMap(mapObject); // REVISED
  ...
};
```

First, I define the `marker` variable by using the `useRef` hook. Then, I replace `marker` in the previous version of the code with `marker.current`. This is because the `useRef` hook creates an object whose `current` property will keep the value across the re-rendering of components (see [React documentation](https://reactjs.org/docs/hooks-reference.html#useref) for detail). It also makes the code more readable: we're now talking about the _current_ value of `marker` at each run of the re-rendering, rather than `marker` which sounds like a constant value.

Now I ask myself: what's the difference between `useRef` and defining a variable outside the component?

Googling this question immediately got me to [Vash (2019)](https://stackoverflow.com/a/57444430/11847654), who explains the difference with an example code. In a nutshell, the difference emerges if I would use more than one `<LocatorButton>` component. By using `useRef`, each instance of the component keeps track of its own value. By defining a variable outside the component, however, all the instances of the component share the same value, which can lead to a weird situation as in [this CodeSandbox example by Vash (2019)](https://codesandbox.io/s/useref-vs-variable-scegg).  

For my case, it doesn't matter as I won't use more than one `<LocatorButton>` component, at least for now. But maybe I will. We never know. So it is safe to use `useRef` to keep track of data across re-rendering.   

## 4. Showing location error range

The GPS functionality of devices cannot perfectly pinpoint the user's location. To indicate the range of error on the map, I want to add a semi-transparent blue circle around the blue circle, as Google Maps app does:

![White-rimmed blue circle shown on Google Maps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0bwd6d00ra6vv1ya1vsc.jpeg)

**A screenshot of Google Maps app in which the semi-transparent blue circle shows the range of error on the user's current location** (image source: [Google Maps Help](https://support.google.com/maps/thread/71969187/my-google-map-cannot-show-direction-direction-pointer-on-blue-dot-is-missing-what-can-i-do?hl=en))

To do so, we first need to extract the GPS information on the range of error. The Geolocation API allows us to get this piece of information in the following way:

```javascript
navigator.geolocation.getCurrentPosition(position => {
  ... 
  const errorRange = position.coords.accuracy; // ADDED
  ...
})
```

where `position.coords.accuracy` gives the radius *in meters* of a circle within which the user's current location falls 95 times out of 100 cases (source: [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates/accuracy)).

To draw this circle, however, we cannot use the Marker object, which doesn't allow us to set its size *in meter*. It took a while for me to figure out how to work around this limitation, but, again from [the source code of Geolocation Marker](https://github.com/ChadKillingsworth/geolocation-marker/blob/master/src/geolocation-marker.js), I've finally learned that the Circle object does the job (see [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/shapes#circles) for detail).

The Circle object works in a similar fashion to the Marker object. So I first check if it's already been added to the map. If so, remove it from the map:

```javascript
const accuracyCircle = useRef(null);  // ADDED
...
navigator.geolocation.getCurrentPosition(position => {
  ... 
  const errorRange = position.coords.accuracy;
  ...
  if (accuracyCircle.current) {           // ADDED
    accuracyCircle.current.setMap(null);  // ADDED
  }                                       // ADDED
})

```

Then, define a new Circle object with the `google.maps.Circle()` method:

```javascript
const accuracyCircle = useRef(null);
...
navigator.geolocation.getCurrentPosition(position => {
  ... 
  const errorRange = position.coords.accuracy;
  ...
  if (accuracyCircle.current) {
    accuracyCircle.current.setMap(null);
  }
  // ADDED FROM HERE
  accuracyCircle.current = new google.maps.Circle({
    center: userLocation,
    fillColor: color['google-blue-dark 100'],
    fillOpacity: 0.4,
    radius: errorRange,
    strokeColor: color['google-blue-light 100'],
    strokeOpacity: 0.4,
    strokeWeight: 1,
    zIndex: 1,
  });
  // ADDED UNTIL HERE
```

where the `center` property refers to the center of the circle (which is set to be `userLocation`, the user's current location), and `radius` to the radius of the circle (which is set to be `errorRange` defined above). The `zIndex` property makes sure that the circle will be overlaid on the blue circle. The other properties define the appearance of the circle (see [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/reference/polygon#CircleOptions) for all the options available for Circle objects) where I define the colors as:

```javascript
// designtokens.js

export const color = {
  'google-blue 100': `#4285F4`,
  'google-blue-dark 100': `#61a0bf`,  // ADDED
  'google-blue-light 100': `#1bb6ff`, // ADDED
  'white 100': `rgb(255,255,255)`,
}
```

These color codes are borrowed from [the source code of Geolocation Marker](https://github.com/ChadKillingsworth/geolocation-marker/blob/master/src/geolocation-marker.js). What's nice about putting all the color codes together in one file is that we can immediately start reconsidering the change of the color palette. Maybe I want to redefine the light and dark variants of `google-blue`. If so, I can just look at this file, rather than searching through the entire codebase.

Finally, I add the circle to the map:

```javascript
const accuracyCircle = useRef(null);
...
navigator.geolocation.getCurrentPosition(position => {
  ... 
  const errorRange = position.coords.accuracy;
  ...
  if (accuracyCircle.current) {
    accuracyCircle.current.setMap(null);
  }
  accuracyCircle.current = new google.maps.Circle({
    center: userLocation,
    fillColor: color['google-blue-dark 100'],
    fillOpacity: 0.4,
    radius: errorRange,
    strokeColor: color['google-blue-light 100'],
    strokeOpacity: 0.4,
    strokeWeight: 1,
    zIndex: 1,
  });
  accuracyCircle.current.setMap(mapObject); // ADDED
});
```

## 5. Improving UX

The code written so far does the basic job to tell the user where they are on the map. There are a few more things to do, however, for enhancing the user experiences.

### 5.1 Using cache up to one second

First, we can use the cached GPS information to make it faster to show the current location. I think 1 second is a reasonable amount of time to keep the cache. Humans walk about 1.4 meters per second (I cannot find the exact source for this data, but many say it's about 1.4 meters per second). The range of location error with my iPhone SE (2nd Gen.) is about 12 meters. Using the location data one second ago, therefore, won't terribly mislocate the user on the map. 

To allow the Geolocation API to use the cached GPS information within the past one second, I add an optional parameter for `getCurrentPosition()`:

```javascript
navigator.geolocation.getCurrentPosition(position => {
  // All the code descirbed in this article so far
  }, {maximumAge: 1000} // ADDED
);

```

where the `maximumAge` option refers to the number of milliseconds to cache the location data (source: [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition)).	 

### 5.2 Flashing the button while waiting

Second, we need to tell the user that the app is working hard to locate where they are, while they are waiting for their location to be shown on the map after tapping the button. It can take a while. If there's no UI change during this waiting time, the user may misunderstand that the app gets frozen or the button doesn't function at all.

To tell the user that the app is working, we can make the trigger button keep flashing until the user's location is shown on the map.

The implementation of this feature requires a long explanation, and it's rather a different topic from the one in this article. So it's described in **Day 13 of this blog series**:

{% link https://dev.to/masakudamatsu/day-13-flashing-tapped-button-while-user-is-waiting-with-react-and-styled-components-1ii4 %}

### 5.3 Error handling

There are four possible errors when we use Geolocation API. When these errors occur, we should tell the user what happens, why it happens, and how they can deal with the error ([Gregory 2021](https://freshsparks.com/user-experience-tips-best-error-messages/)).

I'm still working on exactly how to show these error messages for the user. Making such a dialog in an accessible way is quite a bit of work (see [Giraudel 2021](https://www.smashingmagazine.com/2021/07/accessible-dialog-from-scratch/)). In this article, I only describe how to change the UI state to show error dialogs.

**Geolocation API unsupported**

First, the user's browser may not support Geolocation API. This is unlikely to happen in 2021: the browsers supporting Geolocation API account for 96.78% of global page views in September 2021 ([Can I Use 2021](https://caniuse.com/mdn-api_geolocation)). But just in case.

I set the `status` variable to be `geolocationDenied` in this case:

```javascript
  const getUserLocation = () => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(position => {
      ...
      }, {maximumAge: 1000});
    } else {
      setStatus('geolocationDenied');    // ADDED
    }
  };

```

And then show a dialog explaining what happens if `status` takes the value of `geolocationDenied`.

**Location service permission denied**

Second, the user may have disabled location services with their browser/OS. This happens either immediately after pressing the button (because the user has turned off the location services before) or after the user is asked for permission upon the button click and responds with no. 

This error is likely to happen because not an ignorable number of people are concerned about privacy on the web (e.g., [Newman 2020](https://www.fastcompany.com/90454921/apple-and-googles-tough-new-location-privacy-controls-are-working)). 

If Geolocation API is unable to retrieve user location data because of the disabled location services, the `getCurrentPosition()` method returns the error code equal to 1 (source: [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError)). So we can create an error-handling function and specify it as the optional argument for `getCurrentPosition()`:

```javascript
  const getUserLocation = () => {
    ...
    // ADDED FROM HERE
    const handleGeolocationError(error, setStatus) {
      if (error.code === 1) {
        setStatus('permissionDenied');
      }
    };
    // ADDED UNTIL HERE
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(position => {
        ...
      }, error => {                               // REVISED
        handleGeolocationError(error, setStatus); // REVISED
      }, {maximumAge: 1000}));
    } else {
      setStatus('geolocationDenied')    
    }
  };

```

When the Geolocation API error code is 1, then we set the value of `status` to be `permissionDenied`. We can then render a dialog explaining what happens to the user.

**Geolocation API failure**

Third, the Geolocation API may fail to obtain the user's location data from their device for an unknown reason. It's not clear to me when this can happen. But in this case, the Geolocation API error code is 2. So we can revise the `handleGeolocationError` function as follows:

```jsx
    const handleGeolocationError(error, setStatus) {
      if (error.code === 1) {
        setStatus('permissionDenied');
      } else if (error.code === 2) {      // ADDED
        setStatus('positionUnavailable'); // ADDED
      }
    };
```

Render the corresponding dialog if the `status` takes the value of `positionUnavailable`. 

**Geolocation API not responding**

Finally, there may be a situation where Geolocation API cannot obtain user location data for a long period of time. If this happens, with the current setting, the user cannot tell whether the app is functioning or not.

We should tell the user what is going on. [Kinlan (2019)](https://developers.google.com/web/fundamentals/native-hardware/user-location/) recommends setting a timeout of 10 seconds after which the user gets notified that it took more than 10 seconds to retrieve the location data. To implement this feature, we first need to add `timeout` as an additional optional parameter of the `getCurrentPosition()` method:

```js
    navigator.geolocation.getCurrentPosition(position => {
        ...
      }, error => {
        handleGeolocationError(error, setStatus); 
      }, {maximumAge: 1000, timeout: 10000} // REVISED
    );
```

This will make Geolocation API return the error code of 3 if there is no response after 10,000 milliseconds (i.e., 10 seconds). So I can revise the `handleGeolocationError()` as follows:

```javascript
    const handleGeolocationError(error, setStatus) {
      if (error.code === 1) {
        setStatus('permissionDenied');
      } else if (error.code === 2) {
        setStatus('positionUnavailable');
      } else if (error.code === 3) {
        setStatus('timeout');
      }
    };
```

Then render the corresponding dialog when `status` takes the value of `timeout`.

## Demo

With the code explained in this article (and [**Day 13 of this blog series**](https://dev.to/masakudamatsu/day-13-flashing-tapped-button-while-user-is-waiting-with-react-and-styled-components-1ii4) for flashing the button), I've uploaded [a demo app](https://4aa36604.mima.pages.dev/) to Cloudflare Pages. Try to click the button. When asked for permission to use location services, answer both yes and no, to see how the UI changes. 

If you notice something weird, file a bug report by posting a comment to this article. I'll apprecaite your help to improve _My Ideal Map App_! ;-)

## Next step

If _My Ideal Map App_ were a desktop app, it would be good enough to show the user's location each time the user clicks the button. However, the app is also meant to be used with a smartphone while the user is moving around in a city. It's more desirable for the app to keep track of the user location, updating the marker constantly. Next step is to implement such a feature.

## Changelog

v1.0.1 (Nov 15, 2023): Correct typo.

## References

Can I Use (2021) “[Geolocation API](https://caniuse.com/mdn-api_geolocation)”, _Can I Use?_, accessed on Oct 25, 2021.

Giraudel, Kitty (2021) “[Creating An Accessible Dialog From Scratch](https://www.smashingmagazine.com/2021/07/accessible-dialog-from-scratch/)”, _Smashing Magazine_, Jul 28, 2021.

Gregory, Sonia (2021) “[Best Error Messages: 5 Tips For A User-Friendly Experience](https://freshsparks.com/user-experience-tips-best-error-messages/)”, *FreshSparks*, Sep 26, 2021 (last updated).

Kinlan, Paul (2019) “[User Location](https://developers.google.com/web/fundamentals/native-hardware/user-location/)”, *Web Fundamentals*, Feb 12, 2019.

Kudamatsu, Masa (2021) “[4 Gotchas of embedding Google Maps with Next.js](https://dev.to/masakudamatsu/3-gotchas-of-google-maps-api-when-used-with-next-js-and-eslint-p7e)”, _Dev.to_, Feb 12, 2021.

Newman, Jared (2020) “[Apple and Google’s tough new location privacy controls are working](https://www.fastcompany.com/90454921/apple-and-googles-tough-new-location-privacy-controls-are-working)”, *FastCompany*, Jan 23, 2020.

Vash, Dennis (2019) “[`useRef` will assign a reference for each component, while a variable defined outside a function component scope will only assign once...](https://stackoverflow.com/a/57444430/11847654)”, _Stack Overflow_, Aug 10, 2019.