Day 26: Integrating Google Maps search with a React app
Tags: react, googlemaps, webdev, a11y
Published: 2023-07-02 01:54:48

## TL;DR
To create a user experience like this:

your React app needs to have a component coded as follows (warning: it is very long):
```js
import {useContext, useRef, useState} from 'react';

// for using Google Maps's place ID set by another React component in the app (Section 2.2)
import {PlaceIdContext} from './PlaceIdContext'; 

// for managing UI states (Section 4.1)
import {useStateObject} from './useStateObject'; 

// for handling error responses (Section 2.4)
import FocusLock from 'react-focus-lock';

export const SearchedPlace = ({mapObject}) => {
  // Manage UI changes (Sections 2.2 and 4.1)
  const [state, setState] = useStateObject({ 
    status: 'closed',
    placeData: null,
  });
  const {status, placeData} = state;

  // Receive Google Maps's place ID from another React component in the app (Section 2.2)
  const [placeId, setPlaceId] = useContext(PlaceIdContext);

  // Prepare for dropping a marker to the searched place location (Section 3.3)
  const marker = useRef();

  // Make an API call after the component gets rendered (Section 2.2)
  useEffect(() => {
    if (!placeId) return; 
    setState({status: 'loading'});

    // Remove the marker from Google Maps for the previously searched place (Section 3.3)
    if (marker.current) { 
      marker.current.setMap(null);
    }          

    // Fetch place detail from Google Maps server (Section 2.1)
    const google = window.google;
    const service = new google.maps.places.PlacesService(mapObject);
    const request = {
      placeId: placeId,
      fields: [
        'formatted_address',
        'geometry',
        'name',
        'url',
      ],
    };
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      // Handle successful responses (Section 2.3)
      if (placesServiceStatus === 'OK') {
        const searchedPlace = {
          address: place.formatted_address,
          coordinates: {
            lat: place.geometry.location.lat(),
            lng: place.geometry.location.lng(),
          },
          name: place.name,
          url: place.url,
        };
        // Customize the place marker with an SVG icon(Section 3.4)
        const searchedPlaceMarker = {
          filePath: '/searched-place-mark.svg',
          height: 37.876, 
          width: 39.644,
        }
        marker.current = new google.maps.Marker({
          icon: {
            url: searchedPlaceMarker.filePath,
            anchor: new google.maps.Point(  
              searchedPlaceMarker.width / 2,
              searchedPlaceMarker.height / 2,
            ),            
          },
          // For reopening the place detail popup by cliking the place mark (Section 4.4)
          optimized: false, 
          // Prepare to drop the marker to the searched place location (Section 3.1)
          position: searchedPlace.coordinates,
          title: searchedPlace.name,
        });
        // Allow the user to open the place detail popup by clicking the place mark (Section 4.4)
        marker.current.addListener('click', () => {
          setState({status: 'open'});
        });
        // Drop the marker to the searched place location (Section 3.1)
        marker.current.setMap(mapObject);
        // Snap the map to the area around the searched place (Section 3.2)
        mapObject.panTo(searchedPlace.coordinates);
        // Open the popup window for the detail of the searched place (Section 4.2)
        setState({
          status: 'open',
          placeData: searchedPlace
        });           
      // Handle error responses (Section 2.4)
      } else {
        console.error('Google Maps Place Details API call has failed.');
        setState({status: 'error'});
      }
    }
  }, [mapObject, placeId]);

  // For closing the place detail popup (Section 4.3)
  const closePlaceInfo = () => {
    setState({
      status: 'closed',
    });
  };
  // Close the popup by pressing the outside of it (Section 4.3)
  const dialogDiv = useRef(null); 
  useEffect(() => {
    const listener = event => {
      if (!dialogDiv.current || dialogDiv.current.contains(event.target)) {
        return;
      }
      closePlaceInfo();
    };
    document.addEventListener('pointerdown', listener);
    return () => {
      document.removeEventListener('pointerdown', listener);
    };
  }, [closePlaceInfo, dialogDiv]);
  // Close the popup by pressing the ESC key (Section 4.3)
  useEffect(() => {
    const closeByEsc = event => {
      if (event.key === 'Escape') {
        closePlaceInfo();
      }
    };
    if (status === 'open') {
      document.addEventListener('keydown', closeByEsc);
    } else {
      document.removeEventListener('keydown', closeByEsc);
    }
    return () => {
      document.removeEventListener('keydown', closeByEsc);
    };
  }, [closePlaceInfo, status]);

  // Render no HTML element by default (Section 2.2)
  if status === 'closed' {
    return null;
  // Render a loading message while making an API request (Section 2.2)
  } else if (status === 'loading') {
    return (
      <div>
        <p aria-live="polite" role="status">Getting more information about this place...</p>
      </div>
    )
  // Handle error responses (Section 2.4)
  } else if (status === 'error') {
    return (
      <FocusLock>
        <div role="alertdialog" aria-describedby="error-message" aria-labelledby="error-title">
          <h2 id="error-title">
            Unable to get place detail
          </h2>
          <p id="error-message">
            Google Maps server is currently down. <a
              href="https://status.cloud.google.com/maps-platform/products/i3CZYPyLB1zevsm2AV6M/history"
              rel="noreferrer"
              target="_blank"
            >
              Please check its status
            </a>, and try again once they fix the problem (usually within a few hours).
          </p>
          <button 
            data-autofocus
            onClick={() => setStatus('closed')} 
            type="button"
          >
            Got It
          </button>
        </div>
      </FocusLock> 
    )
  // Show the detail of the searched place in a popup window (Section 4.2)
  } else if (status === 'open') {
    return (
      <FocusLock>
        <div 
          aria-label={placeData.name}
          ref={dialogDiv} // For closing the popup by pressing the outside of it (Section 4.3)
          role="dialog" 
        >
          <h2>{placeData.name}</h2>
          <p>{placeData.address}</p>
          <button 
            data-autofocus 
            onClick={
              /* Click handler to save the searched place 
                 into the user's database in the server 
                 (to be specified in a future post of this blog series) 
              */
            }
            type="button"
          >
            Save
          </button>
          <a href={placeData.url} rel="noreferrer" target="_blank">More Info</a>
          {/* Close the popup by pressing the close button (Section 4.3) */}
          <button aria-label="Close the place detail" onClick={closePlaceInfo} type="button">
            {/* Insert the SVG code for close button icon */}
          </button>
        </div>
      </FocusLock>
    )
  }
}
```
where (1) the `useStateObject` custom hook is defined as
```js
// ./useStateObject.js
// See Section 4.1
import {useReducer} from 'react';
const reducer = (state, action) => ({...state, ...action});
export const useStateObject = initialState => {
  const [state, setState] = useReducer(reducer, initialState);
  return [state, setState];
};
```
(2) the `mapObject` prop is passed from a parent component in which it is defined as
```js
let mapObject;
mapObject = new google.maps.Map(document.getElementById("map"), {
  center: { lat: -34.397, lng: 150.644 }, // or any other location
  zoom: 8, // or any other zoom level
});
```
(3) `PlaceIdContext` is given by
```js
// PlaceIdContext.js
// See Section 2.2
import {createContext, useState} from 'react';

export const PlaceIdContext = createContext();

export function PlaceIdProvider({...props}) {
  const [placeId, setPlaceId] = useState('');
  const value = [placeId, setPlaceId];
  return <PlaceIdContext.Provider value={value} {...props} />;
}
```
and (4) the `placeId` value is set with `setPlaceId()` in another component in charge of Google Maps autocomplete search (which triggers the re-rendering of the `<SearchedPlace>` component defined above).


## 1. Introduction

As far as I know, no one has written online how to integrate Google Maps search into a React app. This article fills this gap in the web dev online community.

The first half of Google Maps search, that is, displaying autocomplete suggestions in response to the user’s query, has already been discussed in [**Day 25 of this blog series**](https://dev.to/masakudamatsu/day-25-adding-google-maps-autocomplete-search-to-a-react-app-2h18). 

The present article focuses the second half of it: what happens after the user selects one of the autocomplete search suggestions. It consists of three parts:
1. Call Google Maps API to retrieve the information of the place the user has selected (Section 2);
2. Drop a pin onto the map (Section 3);
3. Show the information of the searched place (Section 4).

If you are interested only in one of these three topics, skip to the relevant section of this article.

Why am I entitled to write this article? Because I’ve been developing a React app called _My Ideal Map_, which embeds Google Maps and allows the user to search for a place. The code written in this article is what I have discovered as a way to achieve the three above-mentioned aspects of user experience.

## 2. Making an API call to Google Maps server
Let’s first review a vanilla JS version of the code to fetch the data on a place from the Google Maps server (Section 2.1). Then, we adapt the code to React (Section 2.2). Finally, we discuss how to handle successful responses (Section 2.3) and error responses (Section 2.4).
### 2.1 Vanilla JavaScript version
We need the following code snippet to send a network request to the Googel Maps server for fetching the detail of a place whose Google Maps ID is provided as `placeId`:
```js
const google = window.google;
const service = new google.maps.places.PlacesService(mapObject);
const request = {
  placeId: placeId,
  fields: [
    'formatted_address',
    'geometry',
    'name',
    'url',
  ],
};
service.getDetails(request, handleResponse);
function handleResponse(place, placesServiceStatus) {
  // to be completed in the rest of this article
}
```
which is adapted from the code shown in [the Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/places?authuser=2#place_details).

Let’s go through it line by line.

**(1) Setting up**
```js
const google = window.google;
```
This line simply clarifies that we use a global object called `google`. By doing so, I follow the recommendation by [Abramov (2017)](https://stackoverflow.com/a/43718073). If you don’t like it, you can do away with it.

```js
const service = new google.maps.places.PlacesService(mapObject);
```
Here we set up the Places service of Google Maps API so that we can use its method called `getDetails()` to make an API call. Its argument, called `mapObject` here, refers to the object instantiated to embed Google Maps in our web app with the code like this:
```js
mapObject = new google.maps.Map(document.getElementById("map"), {
    center: { lat: -34.397, lng: 150.644 }, // or any other location
    zoom: 8, // or any other zoom level
  });
```
(For how to adapt this code to React, see Section 2 of [Kudamatsu (2021)](https://dev.to/masakudamatsu/3-gotchas-of-google-maps-api-when-used-with-next-js-and-eslint-p7e), one of the popular articles I have written).

Consequently, we need to pass the `mapObject` value from the React component that renders a map. This topic was discussed in Section 1 of [**Day 12 of this blog series**](https://dev.to/masakudamatsu/day-12-showing-user-location-on-embedded-google-maps-with-geolocation-api-and-react-128j), where the `mapObject` was shared with the button to track the user’s location with Geolocation API. So I skip explaining how.

**(2) Specifying which information to fetch**
In the next set of lines:
```js
const request = {
  placeId: placeId,
  fields: [
    'formatted_address',
    'geometry',
    'name',
    'url',
  ],
};
```
I first specify `placeId`, the Google Maps’s identifier for the place selected by the user. This value needs to be passed from a React component that renders autocomplete search (to be discussed in Section 2.2 below).

Then, for the `fields` property, I specify which information on the place to be fetched. Here I go for
1. The place’s name (`name`), 
2. Its longitude and latitude (`geometry`) to drop a pin on the map,
3. Its street address (`formatted_address`) to make sure it is not a place of the same name in a different city, and
4. Its URL on Google Maps app (`url`) to allow the user to see more information on the place if they wish.

There are many other pieces of information on places up for grab from the Google Maps server. For the alphabetical list, see [the Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/places?authuser=2#place_details_results). Beware that some fields (categorised as “[Contact Data](https://developers.google.com/maps/billing-and-pricing/pricing#contact-data)” or “[Atmosphere Data](https://developers.google.com/maps/billing-and-pricing/pricing#atmosphere-data)” incur additional charges (US$3 or $5 per 1,000 requests, respectively) to retrieve.

**(3) Making an API call**
The following line of code will send a request to the Google Maps server:
```js
service.getDetails(request, handleResponse);
```
where the first argument, `request`, refers to the object we have just defined.

The second argument, `handleResponse`, refers to a callback function that will be executed once the response is received from the Google Maps server.

The callback function can be defined before this line of code. For code readability, however, I prefer defining it immediately after, by using the coding technique known as [_hoisting_](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting):
```js
service.getDetails(request, handleResponse);
function handleResponse(place, placesServiceStatus) {
  // to be completed
}
```
where the two arguments, `place` and `placesServiceStatus`, refer to the JSON object for the detailed information of the place and the string value that indicates whether the request is made with success or with an error, respectively.

### 2.2 Adapting to React

Now we know how to make an API call to the Google Maps server. The challenge is how to execute it with React. 

**(1) Handling Place ID with React Context**
One challenge is how to pass the value for `placeId` from another React component. 

The `placeId` value is initially an empty string when the app is launched. Whenever the user chooses one of the autocomplete search suggestions in response to their query, its value changes to Google Maps’s place ID for the place chosen by the user.

To avoid _prop drilling_ (see [Dodds (2018)](https://kentcdodds.com/blog/prop-drilling) for what it is about), I use React Context to keep track of the `placeId` value.

First, create a context provider component:
```js
// PlaceIdContext.js
import {createContext, useState} from 'react';

export const PlaceIdContext = createContext();

export function PlaceIdProvider({...props}) {
  const [placeId, setPlaceId] = useState('');
  const value = [placeId, setPlaceId];
  return <PlaceIdContext.Provider value={value} {...props} />;
}
```
which follows the practice recommended by [Dodds (2021)](https://kentcdodds.com/blog/how-to-use-react-context-effectively#the-custom-provider-component).

Then, wrap the entire app with `<PlaceIdProvider>` so that any React component in the app can consume `placeId` and/or `setPlaceId` via the `useContext` hook in the following way:
```js
import {useContext} from 'react';
import {PlaceIdContext} from './PlaceIdContext';

export const AnyReactComponent = () => {
  const [placeId, setPlaceId] = useContext(PlaceIdContext);
}
```

**(2) SearchBox component**
Another challenge is how to trigger the running of the code for fetching data from the Google Maps server _when_ the user clicks one of the autocomplete search suggestions.

An autocomplete search experience in the app is provided by a React component called `SearchBox`, as described in [**Day 25 of this blog series**](https://dev.to/masakudamatsu/day-25-adding-google-maps-autocomplete-search-to-a-react-app-2h18). Section 5.3 of that article explains how each of the autocomplete suggestions was rendered as an `<li>` element:
```js
<li
  key={item.id}
  {...getItemProps({
    item,
    index
  })}
>
```
where `item.id` refers the place ID that we have been talking about: the identifer to be used to retrieve more information on the place from the Google Maps server. And the `...getItemProps({item, index})`, a _prop getter_ method from [the Downshift library](https://www.downshift-js.com), adds all the attributes necessary for accessibility to the `<li>` element.

Now we add a click event handler to the `<li>` element so that the place ID will be used as a new value of the `placeId` state:
```js
<li
  key={item.id}
  {...getItemProps({
    item,
    index,
    onClick: event => {    // ADDED
      setPlaceId(item.id); // ADDED
    }                      // ADDED
  })}
>
```
where `setPlaceId()` is imported via the `useContext` hook as described above.

This way, whenever the user clicks one of the autocomplete search suggestions, the `placeId` value gets updated, triggering the re-rendering of a component that uses `placeId`, that is, the component that makes a request to the Google Maps server. Which is what we are now going to code.

**(3) Make an API call with `useEffect` hook**
The final challenge is how to send a network request to the Google Maps server once the component gets re-rendered. The short answer is the `useEffect` hook (see [React docs](https://react.dev/reference/react/useEffect#fetching-data-with-effects)) with `placeId` as its dependency.

Let’s create a new React component called `SearchedPlace` which is in charge of rendering the place the user has selected among autocomplete search results:
```js
import {useState} from 'react';
export const SearchedPlace = ({mapObject}) => {
  const [status, setStatus] = useState('closed');
  if status === 'closed' {
    return null;
  }
}
```
Its UI state is by default `'closed'`, in which case no HTML element is rendered. Its prop `mapObject` refers to the incidence of an embedded Google Maps as described in Section 2.1 above.

Now let’s add the code to make an API call:
```js
import {useContext, useState} from 'react'; // REVISED
import {PlaceIdContext} from './PlaceIdContext'; // ADDED

export const SearchedPlace = ({mapObject}) => {
  const [status, setStatus] = useState('closed');

  // ADDED FROM HERE
  const [placeId, setPlaceId] = useContext(PlaceIdContext);
  useEffect(() => {
    const google = window.google;
    const service = new google.maps.places.PlacesService(mapObject);
    const request = {
      placeId: placeId,
      fields: [
        'formatted_address',
        'geometry',
        'name',
        'url',
      ],
    };
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      // To be completed in the rest of this article
    }
  }, [mapObject, placeId]);
  // ADDED UNTIL HERE

  if status === 'closed' {
    return null;
  }
}
```
We need to wrap the entire code for making an API call inside the `useEffect` hook, which runs after the component gets rendered. This is because the `google` object does not exist until the component gets rendered. 

However, the above code returns an error when `placeId` is an empty string. In such a case, we should skip running the code:
```js
  ...
  useEffect(() => {
    if (!placeId) return; // ADDED
    const google = window.google;
    ...
```
**(4) Loading message**
Also, the network connection can be slow. To tell the user that the app is in the process of retrieving information from the Google Maps server, let’s update the UI once the `useEffect` hook starts running:
```js
import {useContext, useState} from 'react'; 
import {PlaceIdContext} from './PlaceIdContext'; 

export const SearchedPlace = ({mapObject}) => {
  const [status, setStatus] = useState('closed');
  const [placeId, setPlaceId] = useContext(PlaceIdContext);
  useEffect(() => {
    if (!placeId) return;

    setStatus('loading'); // ADDED

    const google = window.google;
    const service = new google.maps.places.PlacesService(mapObject);

    // This part of the code is omitted for brevity

  }, [mapObject, placeId]);

  if status === 'closed' {
    return null;
  // ADDED FROM HERE
  } else if (status === 'loading') {
    return (
      <div>
        <p aria-live="polite" role="status">Getting more information about this place...</p>
      </div>
    )
  // ADDED UNTIL HERE
  }
}
```
I cannot find the definitive information on how to make loading messages accessible. But [Bischoff (2016)](https://stackoverflow.com/a/38599919) suggests adding `aria-live="polite"` (and optionally `role="status"`) to the element that contains the loading message text. I’ve tested this solution with VoiceOver, Mac OS’s screen reader. The loading message does get announced if it takes time to load, and the announcement gets interrupted as soon as the searched place information popup becomes visible.

However, [Golcic (2020)](https://ux.stackexchange.com/q/131889) argues (1) `role="status"` lacks cross-browser consistency in its implementation and (2) the element with `aria-live` has to be in the DOM on page load, rather than being added by JavaScript. 

Let me know what you think if you are an accessibility expert.

By the way, the wrapping `<div>` is redundant, but it’s likely to be useful to style the box that surrounds the loading message text.

---
Now let’s move on to handle responses from the Google Maps server.

### 2.3 Handling successful responses
The Google Maps server returns two variables if the network request is successful. These two variables can be named in any way, but let’s call them `placesServiceStatus` and `place`.

**(1) `placesServiceStatus` variable**
This variable equals to `OK` if the network request has been successful. (For other values, see [the Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/places?authuser=2#place_details_status_codes).)

To handle successful and error cases separately, therefore, we can code the callback function for the `getDetails()` method as follows:
```js
    ...
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      // ADDED FROM HERE
      if (placesServiceStatus === 'OK') {
        // to be completed
      } else {
        console.error('Google Maps Place Details API call has failed.');
      }
      // ADDED UNTIL HERE
    }
    ...
```
**(2) `place` JSON object**
In case of the successful request, the Google Maps server also returns the `place` JSON object, which in our case is something like this:
```json
{
  "formatted_address" : "3-16 Sagatenryūji Susukinobabachō, Ukyo Ward, Kyoto, 616-8385, Japan",
  "geometry" : {
    "location" : {
      "lat" : 35.013867,
      "lng" : 135.6762773
    },
    "viewport" : {
      "northeast" : {
          "lat" : 35.0151522302915,
          "lng" : 135.6777188302915
      },
      "southwest" : {
          "lat" : 35.0124542697085,
          "lng" : 135.6750208697085
      }
    }
  },
  "name" : "Fukuda Art Museum",
  "url" : "https://maps.google.com/?cid=540503174389752899"
},
```
Note that the top-level properties correspond to the fields that we have specified in the getDetails() method:
```js
...
const request = {
  placeId: placeId,
  fields: [
    'formatted_address',
    'geometry',
    'name',
    'url',
  ],
};
service.getDetails(request, handleResponse);
...
```

By specifying the callback function further, we can process this response to be used for dropping a pin on the map (see Section 3 below) and displaying the place information (see Section 4 below):
```js
  ...
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      if (placesServiceStatus === 'OK') {
        // ADDED FROM HERE
        const searchedPlace = {
          address: place.formatted_address,
          coordinates: {
            lat: place.geometry.location.lat(),
            lng: place.geometry.location.lng(),
          },
          name: place.name,
          url: place.url,
        };
        // ADDED UNTIL HERE
      } else {
        console.error('Google Maps Place Details API call has failed.');
      }
    }
  ...
```

### 2.4 Handling error responses
In the unlikely event of the Google Maps server returning an error response, we need to render an error message so that the user can learn what is going on.

First, let’s update the `status` variable to `"error"` if `placesServiceStatus` is not `'OK'`:
```js
  ...
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      if (placesServiceStatus === 'OK') {
        const searchedPlace = {
          address: place.formatted_address,
          coordinates: {
            lat: place.geometry.location.lat(),
            lng: place.geometry.location.lng(),
          },
          name: place.name,
          url: place.url,
        };
      } else {
        console.error('Google Maps Place Details API call has failed.');
        setStatus('error'); // ADDED
      }
    }
  ...
```

Then render an `alertdialog` div that shows an error message:
```js
  ...
  if status === 'closed' {
    return null;
  } else if (status === 'loading') {
    return (
      <div>
        <p aria-live="polite" role="status">Getting more information about this place...</p>
      </div>
    )
  // ADDED FROM HERE
  } else if (status === 'error') {
    return (
      <div role="alertdialog" aria-describedby="error-message" aria-labelledby="error-title">
        <h2 id="error-title">
          Unable to get place detail
        </h2>
        <p id="error-message">
          Google Maps server is currently down. <a
            href="https://status.cloud.google.com/maps-platform/products/i3CZYPyLB1zevsm2AV6M/history"
            rel="noreferrer"
            target="_blank"
          >
            Please check its status
          </a>, and try again once they fix the problem (usually within a few hours).
        </p>
        <button 
          onClick={() => setStatus('closed')} 
          type="button"
        >
          Got It
        </button>
      </div>
    )
  // ADDED UNTIL HERE
  }
  ...
```
However, the above code is not enough for an HTML element with `role="alertdialog"` to announce its text content via the `aria-labelledby` and `aria-describedby` attributes. When it is opened, the `alertdialog` element needs to have its child interactive element _auto-focused_.

The above code is also insufficient because the error dialog needs to be _modal_, that is, the user must not be able to move the focus to other interactive elements outside the dialog. 

To achieve this set of focus management for modal dialogs, my preferred solution is to use the [`react-focus-lock`](https://github.com/theKashey/react-focus-lock#react-focus-lock) library:
```js
import FocusLock from 'react-focus-lock'; // ADDED

...

  ...
  } else if (status === 'error') {
    return (
      <FocusLock> {/* ADDED */}
        <div role="alertdialog" aria-describedby="error-message" aria-labelledby="error-title">
          <h2 id="error-title">
            Unable to get place detail
          </h2>
          <p id="error-message">
            Google Maps server is currently down. <a
              href="https://status.cloud.google.com/maps-platform/products/i3CZYPyLB1zevsm2AV6M/history"
              rel="noreferrer"
              target="_blank"
            >
              Please check its status
            </a>, and try again once they fix the problem (usually within a few hours).
          </p>
          <button 
            data-autofocus // ADDED
            onClick={() => setStatus('closed')} 
            type="button"
          >
            Got It
          </button>
        </div>
      </FocusLock> {/* ADDED */}
    )
  // ADDED UNTIL HERE
  }
  ...
```
The entire `alertdialog` element is wrapped with the `<FocusLock>` component, to trap the focus within it. Plus, the `data-autofocus` attribute is added to the `button` element so that, thanks to the `react-focus-lock` library, the button will be auto-focused when the `alertdialog` element is rendered. Very simple, isn’t it? (The `inert` attribute is the future way to trap the focus (see [Friedman 2022](https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/#accessible-modals)), but its browser support is slightly below 90% of global page views in June 2023 according to [_Can I Use?_](https://caniuse.com/mdn-api_htmlelement_inert). So I would rather wait for a bit.)

Final note: the error message itself can surely be improved. In particular, it can change its text depending on the `placesServiceStatus` value. This is a topic that deserves one single article which I will write in the future.

## 3. Dropping a place mark on the map
Now we want to drop a marker on the embedded Google Maps to indicate the location of the place selected by the user among autocomplete search suggestions.

For this purpose, we need the following two pieces of data fetched from the Google Maps server with the code described in the previous section (where unnecessary data are temporarily commented out):
```js
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      if (placesServiceStatus === 'OK') {
        const searchedPlace = {
          // address: place.formatted_address,
          coordinates: {
            lat: place.geometry.location.lat(),
            lng: place.geometry.location.lng(),
          },
          name: place.name,
          // url: place.url,
        };
      } else {
        console.error('Google Maps Place Details API call has failed.');
        setStatus('error');
      }
    }
```
We therefore refer to the place’s name as `searchedPlace.name` and its location as `searchedPlace.coordinates` in this section. First we set the location to which the place mark is dropped (Section 3.1). Then we snap the map to the area round it (Section 3.2). Third, we handle the removal of the mark on the previously searched place (Section 3.3), which turns out to be a bit tricky with React. Finally, we customize the place mark with an SVG image (Section 3.4).

### 3.1 Specify the location
To drop a marker on embedded Google Maps, we use the following code snippet (see [Google Maps Platform documentation for detail](https://developers.google.com/maps/documentation/javascript/markers#add)):
```js
const marker = new google.maps.Marker({
  position: searchedPlace.coordinates,
  title: searchedPlace.name,
});
marker.setMap(mapObject);
```
where `position` sets the latitude and longitude of the place marker with an object whose properties are `lat` and `lng`, and `title` sets the text to pop up as a tooltip when the user hovers over the place mark.

Once the `marker` object is created, then we can execute its method `setMap(mapObject)` to render the place mark on the map, where `mapObject` refers to the instance of the embedded Google Maps (see Section 2.1 above).

### 3.2 Snap the map to the searched place
In addition to dropping a place mark onto the searched place, we need to snap the map to that location; otherwise the user would not be able to see the dropped marker. 

To do so, we use the `panTo()` method of `mapObject`:
```js
const marker = new google.maps.Marker({
  position: searchedPlace.coordinates,
  title: searchedPlace.name,
});
marker.setMap(mapObject);
mapObject.panTo(searchedPlace.coordinates); // ADDED
```
With this code, the map will show the searched place at the center of the screen.

There is another method called `setCenter()`, which does the same job. But `panTo()` animates the movement of the map more smoothly than `setCenter()` does ([danhardman 2014](https://stackoverflow.com/a/26677484)).

### 3.3 Remove the marker for the previously searched place
If the user searches for a place only once, the above code works just fine. In reality, the app should allow the user to repeat their search. The code written so far does not erase the marker for the previously searched places.

With React, the removal of place markers requires the `useRef` hook, as explained in extensive detail in Section 3.2 of [**Day 12 of this blog post series**](https://dev.to/masakudamatsu/day-12-showing-user-location-on-embedded-google-maps-with-geolocation-api-and-react-128j).

The idea is that, instead of creating a constant variable `marker` inside the `useEffect` hook, generate a `marker` variable outside the `useEffect` hook with `useRef`. Then assign the instance of a marker object to `marker.current` each time the `useEffect` hook is run. If `marker.current` is already assigned, remove it from the map with `marker.current.setMap(null)`:
```js
import {useContext, useRef, useState} from 'react'; // REVISED
import {PlaceIdContext} from './PlaceIdContext'; 

export const SearchedPlace = ({mapObject}) => {
  const [status, setStatus] = useState('closed');
  const [placeId, setPlaceId] = useContext(PlaceIdContext);

  const marker = useRef(); // ADDED 

  useEffect(() => {
    if (!placeId) return;
    setStatus('loading');

    if (marker.current) {           // ADDED 
      marker.current.setMap(null);  // ADDED 
    }                               // ADDED 

    const google = window.google;
    const service = new google.maps.places.PlacesService(mapObject);
    const request = {
      placeId: placeId,
      fields: [
        'formatted_address',
        'geometry',
        'name',
        'url',
      ],
    };
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      if (placesServiceStatus === 'OK') {
        const searchedPlace = {
          address: place.formatted_address,
          coordinates: {
            lat: place.geometry.location.lat(),
            lng: place.geometry.location.lng(),
          },
          name: place.name,
          url: place.url,
        };
        // REVISED FROM HERE
        marker.current = new google.maps.Marker({
          position: searchedPlace.coordinates,
          title: searchedPlace.name,
        });
        marker.current.setMap(mapObject);
        // REVISED UNTIL HERE
        mapObject.panTo(searchedPlace.coordinates);

      } else {
        console.error('Google Maps Place Details API call has failed.');
        setStatus('error');
      }
    }
  }, [mapObject, placeId]);

  // the rest of the code omitted for brevity

}
```
The reason behind this way of coding is that each time the `SearchedPlace` component gets re-rendered, the `marker` variable would otherwise be re-defined, preventing us from accessing the previous marker object to remove it from the map. The `useRef` will retain the value even when the component gets re-rendered.

### 3.4 Customize the marker
Finally, we customize the appearance of the place maker by using an SVG image. To do so, add `icon` property to the `marker` object, which needs to refer to an object with `url` property:

```js
marker.current = new google.maps.Marker({
  // ADDED FROM HERE
  icon: {
    url: '/searched-place-mark.svg',
  },
  // ADDED UNTIL HERE
  position: searchedPlace.coordinates,
  title: searchedPlace.name,
});
marker.current.setMap(mapObject);
```
where we assume that the SVG image file, `searched-place-mark.svg`, is saved in the root directory of the website.

This is not enough, however. By default, Google Maps anchors an SVG image at its top-left corner. This means that, when the user zooms the map in and out, the place mark appears to move around over the map.

To fix this behavior, the SVG image needs to be anchored at its center. The SVG image contains its size information in its source code: the `height` and `width` attributes of the `<svg>` element. So we can copy and paste the `height` and `width` attribute values:
```js
const searchedPlaceMarker = {
  height: 37.876,
  width: 39.644,
}
```
Then use these values to anchor the SVG image at its center:
```js
icon: {
  url: '/searched-place-mark.svg',
  // ADDED FROM HERE
  anchor: new google.maps.Point(
    searchedPlaceMarker.width / 2,
    searchedPlaceMarker.height / 2,
  ),                                    
  // ADDED UNTIL HERE
},
```
where `new google.maps.Point()` is Google Maps's API function to specify a coordinate. 

For more options for the `icon` property, see [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/markers#complex_icons).

The above code works, but to make the code more readable, let’s define the file path as the `filePath` property of the `searchedPlaceMarker` object:
```js
const searchedPlaceMarker = {
  filePath: '/searched-place-mark.svg',   // ADDED
  height: 37.876, 
  width: 39.644,
}
marker.current = new google.maps.Marker({
  icon: {
    url: searchedPlaceMarker.filePath,  // REVISED
    anchor: new google.maps.Point(  
      searchedPlaceMarker.width / 2,
      searchedPlaceMarker.height / 2,
    ),            
  },
  position: searchedPlace.coordinates,
  title: searchedPlace.name,
});
marker.current.setMap(mapObject);
mapObject.panTo(searchedPlace.coordinates);
```
This way, in case we need to change the file path or replace the SVG image with another, we just need to revise the code for the `searchedPlaceMarker` object, with the implementation code intact.

## 4. Showing the place detail
Now we want to show the information of the place the user has selected among Google Maps autocomplete search suggestions. 

We first discuss how to manage React states to update the UI (Section 4.1). Then we render the information of the searched place in a popup window (Section 4.2). Third, we allow the user to close the popup (Section 4.3). Finally, we also allow the user to reopen the popup by pressing the place mark on the embedded Google Maps (Section 4.4).

### 4.1 React state for place data
Since it is a change in UI to show the information of the place searched by the user, we need to re-render the React component to reflect the UI change. To do so, we need to update a React state for this component. 

But re-rendering will lose the data fetched from the Google Maps server. One way to go about it is to add another state to hold the data:
```js
...
export const SearchedPlace = ({mapObject}) => {
  ...
  const [status, setStatus] = useState('closed');
  const [placeData, setPlaceData] = useState(null); // ADDED
  ...
```
But whenever the `placeData` gets updated, we also want to update `status` so the place information will get revealed. So it’s best to group them together as an object:
```js
...
export const SearchedPlace = ({mapObject}) => {
  ...
  const [state, setState] = useState({
    status: 'closed',
    placeData: null,
  });
  const {status, placeData} = state;
  ...
```
The second line destructures the `state` object so that our code written so far will not break due to the use of `status`. (This is a technique I learned from [Epic React](https://epicreact.dev/learn)’s “React Hooks” module.)

However, with the `useState` hook, we always need to specify both property values whenever we update the `state` variable, like this:
```js
setState({
  status: 'loading',
  placeData: null,
})
``` 
which requires an extra redundant line of code. Plus, in case I mistakenly write the code as follows
```js
setState({
  status: 'loading',
})
```
the `placeData` property is gone, throwing an error where `placeData` is used. 

My workaround is to create a custom hook that I call `useStateObject`, with the help of the `useReducer` hook (the generalized version of the `useState` hook):
```js
// ./useStateObject.js
import {useReducer} from 'react';
const reducer = (state, action) => ({...state, ...action});
export const useStateObject = initialState => {
  const [state, setState] = useReducer(reducer, initialState);
  return [state, setState];
};
```
With this custom hook, if we execute
```js
setState({status: 'loading'});
```
then the `placeData` property remains intact. (This is a technique I learned from [Epic React](https://epicreact.dev/learn)’s “Advanced React Hooks” module.)

So the code written so far in the previous sections is refactored as follows:
```js
import {useContext, useState} from 'react'; 
import {PlaceIdContext} from './PlaceIdContext'; 
import {useStateObject} from './useStateObject'; // ADDED

export const SearchedPlace = ({mapObject}) => {
  const [placeId, setPlaceId] = useContext(PlaceIdContext);
  // REVISED FROM HERE
  const [state, setState] = useStateObject({
    status: 'closed',                       
    placeData: null,                        
  });                                        
  const {status, placeData} = state;         
  // REVISED UNTIL HERE
  useEffect(() => {
    if (!placeId) return;
    setState({status: 'loading'}); // REVISED
    const google = window.google;
    const service = new google.maps.places.PlacesService(mapObject);
    const request = {
      placeId: placeId,
      fields: [
        'formatted_address',
        'geometry',
        'name',
        'url',
      ],
    };
    service.getDetails(request, handleResponse);
    function handleResponse(place, placesServiceStatus) {
      if (placesServiceStatus === 'OK') {
      
        // Omitted for brevity

      } else {
        console.error('Google Maps Place Details API call has failed.');
        setState({status: 'error'}); // REVISED
      }
    }
  }, [mapObject, placeId]);

  if status === 'closed' {
    return null;
  } else if (status === 'loading') {
    return (
      <div>
        <p aria-live="polite" role="status">Getting more information about this place...</p>
      </div>
    )
  } else if (status === 'error') {
    return (
      {/* Omitted for brevity */}
    )
  }
}
```

### 4.2 Render the place data in a popup window
Then, after fetching the place data from the Google Maps server and dropping a place mark onto its location, we update `placeData` with the fetched data:
```js
service.getDetails(request, handleResponse);
function handleResponse(place, placesServiceStatus) {
  if (placesServiceStatus === 'OK') {
    const searchedPlace = {
      address: place.formatted_address,
      coordinates: {
        lat: place.geometry.location.lat(),
        lng: place.geometry.location.lng(),
      },
      name: place.name,
      url: place.url,
    };

    ... // omitted for brevity

    marker.current.setMap(mapObject);
    mapObject.panTo(searchedPlace.coordinates);
    // ADDED FROM HERE
    setState({                
      status: 'open',         
      placeData: searchedPlace
    });
    // ADDED UNTIL HERE
  } else {
    console.error('Google Maps Place Details API call has failed.');
    setState({status: 'error'});
  }
}
```
where I also update `status` to the string value of `'open'`, to switch the HTML element to render from the loading message to the place information popup.

Now the `searchedPlace` component gets re-rendered with a new value of `placeData`, which is used to render HTML:
```js
...
  if status === 'closed' {
    return null;
  } else if (status === 'loading') {
    return (
      <div>
        <p aria-live="polite" role="status">Getting more information about this place...</p>
      </div>
    )
  } else if (status === 'error') {
    return (
      <FocusLock>
        <div role="alertdialog" aria-describedby="error-message" aria-labelledby="error-title">
          <h2 id="error-title">
            Unable to get place detail
          </h2>
          <p id="error-message">
            {/* Error message text omitted for brevity */}
          </p>
          <button 
            data-autofocus
            onClick={() => setStatus('closed')} 
            type="button"
          >
            Got It
          </button>
        </div>
      </FocusLock> 
    )
  {/* ADDED FROM HERE */}
  } else if (status === 'open') {
    return (
      <FocusLock>
        <div 
          aria-label={placeData.name}
          role="dialog" 
        >
          <h2>{placeData.name}</h2>
          <p>{placeData.address}</p>
          <button 
            data-autofocus 
            onClick={
              /* Click handler to save the searched place 
                 into the user's database in the server 
                 (to be specified in a future post of this blog series) 
              */
            }
            type="button"
          >
            Save
          </button>
          <a href={placeData.url} rel="noreferrer" target="_blank">More Info</a>
        </div>
      </FocusLock>
    )
  {/* ADDED UNTIL HERE */}
  }
```
Like what we have done with the error message dialog in Section 2.4 above, we wrap the entire dialog with the `<FocusLock>` component from the [`react-focus-lock`](https://github.com/theKashey/react-focus-lock#react-focus-lock) library. This way the keyboard user cannot move the focus outside the popup by pressing the Tab key until they close the popup (which will be implemented below in Section 4.3). I believe that this is what the keyboard user expects when a popup shows up.

Also, again like what we have done with the error message dialog, the `data-autofocus` attribute, together with the `<FocusLock>` component, specifies the HTML element to be auto-focused when the popup is rendered. I add this attribute to the `<button>` element which will act as a button to save the searched place into the user’s database in the server (to be discussed in the future post of this blog series)

With `<div role="dialog">` alongside the focus moving into its child element, the screen reader will announce the accessible name of the `<div>` element, which is the place name specified with `aria-label`. You may wonder why I don’t use `aria-labelledby` to refer to the `<h2>` element instead. I tried this approach, but MacOS screen reader, VoiceOver, _does not_ announce the place name. According to [Chapman (2022)](https://stackoverflow.com/a/71645490), this happens if the element referred to by the `aria-labelledby` attribute is not yet rendered when the `<div role="dialog">` gets rendered. A work around, also suggested by [Chapman (2022)](https://stackoverflow.com/a/71645490), is to use `aria-label`.

In addition, I add the link to Google Maps’s own page on the place that will open in a new tab, with `target="_blank"`. Usually, forcing the user to open a link in a new tab is not a good UX practice ([Coyier 2014](https://css-tricks.com/use-target_blank/), [Roselli 2020](https://adrianroselli.com/2020/02/link-targets-and-3-2-5.html)). However, in this particular case, we do not want to kill the app’s active session by taking the user to the linked page on the same browser tab. 

But whenever we use `target="_blank"`, we should also use `ref="noreferrer"` so that the server of a linked page will not be able to see the current page URL which may contain privacy information such as the user’s name ([MDN Contributors 2023](https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns)).

### 4.3 Close the place detail popup
The user may want to close the place detail popup and to explore the area around it on the embedded Google Maps: maybe there are some places that the user has saved before.

My Ideal Map provides three ways to close the place detail popup:
(1) Press the close button at the top-right corner of the popup;
(2) Tap the area outside the popup; and
(3) Press the Esc key in the keyboard.

To implement these user experiences, let’s first define the function to close the popup.
```js
const closePlaceInfo = () => {
  setState({
    status: 'closed',
  });
};
```
By setting the `status` property of the `state` object to be `"closed"`, the `SearchedPlace` component gets rerendered to return `null` in the following part of the code:
```js
...
  if status === 'closed' {
    return null;
  } else if (status === 'loading') {
...
```
So it removes the HTML elements that consititute the popup.

**(1) Close button**
Now we add a button to run this function to the place detail popup:
```js
  ...
  } else if (status === 'open') {
    return (
      <div 
        aria-label={placeData.name}
        role="dialog" 
      >
        <h2>{placeData.name}</h2>
        <p>{placeData.address}</p>
        <a href={placeData.url} rel="noreferrer" target="_blank">More Info</a>
        <button aria-label="Close the place detail" onClick={closePlaceInfo} type="button">
          {/* Insert the SVG code for close button icon */}
        </button>
      </div>
    )
  }
  ...
```
For more detail on the button to close a popup, see [**Day 18 of this blog series**](https://dev.to/masakudamatsu/day-18-building-a-close-button-with-ripple-effect-as-a-react-component-1n62).

**(2) Outside the popup**
To close the popup by pressing anywhere outside of it, I adapt the approach proposed by [Akymenko (2019)](https://css-tricks.com/hamburger-menu-with-a-side-of-react-hooks-and-styled-components/#aa-close-the-menu-by-clicking-outside-of-it). 

We first need to refer to the popup `<div>` with the `useRef` hook:
```js
export const SearchedPlace = ({mapObject}) => {
  ...

  const dialogDiv = useRef(null); // ADDED

  if status === 'closed' {
    return null;
  } else if (status === 'loading') {
    {/* Omitted for brevity */}
  } else if (status === 'error') {
    {/* Omitted for brevity */}
  } else if (status === 'open') {
    return (
      <div 
        aria-label={placeData.name}
        ref={dialogDiv} // ADDED
        role="dialog" 
      >
        <h2>{placeData.name}</h2>
        <p>{placeData.address}</p>
        <a href={placeData.url} rel="noreferrer" target="_blank">More Info</a>
        <button aria-label="Close the place detail" onClick={closePlaceInfo} type="button">
          {/* Insert the SVG code for close button icon */}
        </button>
      </div>
    )
  }  
}
```
Then run an additional `useEffect` hook to attach the `pointerdown` event handler to the `document` object:
```js
  const dialogDiv = useRef(null);
  // ADDED FROM HERE
  useEffect(() => {
    const listener = event => {
      if (!dialogDiv.current || dialogDiv.current.contains(event.target)) {
        return;
      }
      closePlaceInfo();
    };
    document.addEventListener('pointerdown', listener);
    return () => {
      document.removeEventListener('pointerdown', listener);
    };
  }, [closePlaceInfo, dialogDiv]);
  // ADDED UNTIL HERE

  if status === 'closed' {
    return null;
  } else if (status === 'loading') {
  ...
```
The crucial part in the above code is
```js
dialogDiv.current.contains(event.target)
```
This returns `true` if the `event.target` (the HTLM element that receives the user’s interaction) refers to the `dialogDiv` itself or any of its child elements, that is, when the user clicks somewhere _inside_ the popup. Otherwise, it returns `false` and thus the `useEffect` hook continues to run and execute `closePlaceInfo()` to close the popup.

And this event handler is attached to the `pointerdown` event of the `document` object, which means both the desktop user clicks anywhere in the app and the mobile user taps anywhere in the app.

To remove this event handler whenever the `SearchedPlace` component gets dismounted, we return the function to remove the event listener — the standard practice to use the `useEffect` hook (see [React documentation](https://react.dev/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed)).

**(3) The ESC key**
To close the popup by pressing the Esc key in the keyboard, we actually use the `useEffect` hook in a similar fashion:
```js
  useEffect(() => {
    const closeByEsc = event => {
      if (event.key === 'Escape') {
        closePlaceInfo();
      }
    };
    if (status === 'open') {
      document.addEventListener('keydown', closeByEsc);
    } else {
      document.removeEventListener('keydown', closeByEsc);
    }
    return () => {
      document.removeEventListener('keydown', closeByEsc);
    };
  }, [closePlaceInfo, status]);
```
The event handler, `closeByEsc`, checks if the user presses the Esc key (i.e., if `event.key === 'Escape'` is `true`) and, if so, executes the `closePlaceInfo` function to close the popup. And this event handler will be attached to the `document` object if `status==='open'` is `true`, that is, when the popup is opened. It will be removed if `status` takes another value, that is, when the popup is closed. By having `status` as its dependency, this `useEffect` hook runs every time the `status` gets updated.

Since this event only concerns the keyboard user, we use `keydown` event, rather than `pointerdown` event.

### 4.4 Reopen the popup by pressing the place mark
The place detail popup can now be closed by the user. In case the user wants to see it again, we let them do so by clicking the place mark on the embedded Google Maps.

To add a click handler to the place mark, we revise the `handleResponse` callback function as follows:
```js
service.getDetails(request, handleResponse);
function handleResponse(place, placesServiceStatus) {
  if (placesServiceStatus === 'OK') {
    const searchedPlace = {
      // Omitted for brevity
    };
    const searchedPlaceMarker = {
      // Omitted for brevity
    }
    marker.current = new google.maps.Marker({
      icon: {
        url: searchedPlaceMarker.filePath,
        anchor: new google.maps.Point(  
          searchedPlaceMarker.width / 2,
          searchedPlaceMarker.height / 2,
        ),            
      },
      optimized: false, // ADDED
      position: searchedPlace.coordinates,
      title: searchedPlace.name,
    });

    // ADDED FROM HERE
    marker.current.addListener('click', () => {
      setState({status: 'open'});
    });
    // ADDED UNTIL HERE    

    marker.current.setMap(mapObject);
    mapObject.panTo(searchedPlace.coordinates);
    setState({          
      status: 'open',    
      placeData: searchedPlace
    });
  } else {
    // Omitted for brevity
  }
}
```
The `marker` object has a method `addListener()` which attaches an event listener to the place mark.

By having an event listener attached, the place mark on embedded Google Maps becomes accessible: it is rendered as a `<button>` element focusable with the Tab key with the `title` property value used as its accessible name. For this purpose, however, there is one extra thing to do, according to the [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/markers#accessible): add `optimized: false` to the set of options to create a marker in the first place. By default, this option is `true`, which means a group of markers may be rendered as a single marker for performance (see [Google Maps Platform documentation](https://developers.google.com/maps/documentation/javascript/markers#optimize)). We need to avoid this from happening.

---

Now style the popup as you wish. In my case, I style it like a cloud floating above city streets (see [**Day 19 of this blog series**](https://dev.to/masakudamatsu/day-19-styling-a-popup-like-clouds-2m04)).

## 5. Final words
That’s all! What a long article needed to explain how to show a searched place on the Google Maps embedded in a React app...

### No demo available...
I apologize for the lack of a demo of the code described in this article. This is because Google Maps charges me every time the user loads a map and fetches the data on places via the web app that embeds Google Maps.

## References
Abramov, Dan (2017) “[As mentioned in the user guide, you need to explicitly read any global variables from `window`...](https://stackoverflow.com/a/43718073)”, _Stack Overflow_, May 1, 2017.

Akymenko, Maks (2019) “[Hamburger Menu with a Side of React Hooks and Styled Components](https://css-tricks.com/hamburger-menu-with-a-side-of-react-hooks-and-styled-components/)”, _CSS-Tricks_, Sep 10, 2019.

Bischoff, Ashley (2016) “[`aria-live` triggers screen readers when an element with `aria-live` (or text within an element with `aria-live`) is added or removed from the DOM...](https://stackoverflow.com/a/38599919)”, _Stack Overflow_, Jul 26, 2016.

Chapman, George (2022) “[There's nothing wrong with the way you're using aria-labelledby, however it may be that the content of your modal (`#modal-desc`) is being added after the modal container (`#modal`)...](https://stackoverflow.com/a/71645490)”, _Stack Overflow_, Mar 28, 2022.

Coyier, Chris (2014) “[When to use target=”_blank”](https://css-tricks.com/use-target_blank/)”, _CSS-Tricks_, Jan 15, 2014.

danhardman (2014) “[According to the reference: panTo: Changes the center of the map to the given LatLng...](https://stackoverflow.com/a/26677484)”, _Stack Overflow_, Oct 31, 2014.

Dodds, Kent C. (2018) “[Prop Drilling](https://kentcdodds.com/blog/prop-drilling)”, _kentcdodds.com_, May 21, 2018.

Dodds, Kent C. (2021) “[How to use React Context effectively](https://kentcdodds.com/blog/how-to-use-react-context-effectively)”, _kentcdodds.com_, Jun 5, 2021.

Friedman, Vitaly (2022) “[A Complete Guide To Accessible Front-End Components](https://www.smashingmagazine.com/2021/03/complete-guide-accessible-front-end-components/)”, _Smashing Magazine_, May 25, 2022.

Golcic, Hrvoje (2020) “[Accessible way of notifying a screen reader about loading the dynamic Web page update (AJAX)](https://ux.stackexchange.com/q/131889)”, _Stack Exchange_, Feb 28, 2020.

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

MDN Contributors (2023) “[Referer header: privacy and security concerns](https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns)”, _MDN Web Docs_, Mar 2, 2023 (last updated).

Roselli, Adrian (2020) “[Link Targets and 3.2.5](https://adrianroselli.com/2020/02/link-targets-and-3-2-5.html)”, _adrianroselli.com_, Feb 7, 2020.