I've set up Google Maps JavaScript API for my Next.js app. The API documentation on how to get started (Google 2021) is very well-written, but I encountered a few gotchas when the API is used together with Next.js and also ESLint. Let me take note of them below for your information (and for my future self).
Gotcha #1: CSS
TL;DR
Add the following CSS declaration:
#__next {
height: 100%;
}
Detail
To show a Google Map across the entire browser window, Google (2021) recommends the following CSS code:
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
where #map
is the id
for the container element in which a Google Map will be shown.
With Next.js, however, the #map
container will not be a direct child of the body
element. There will be another div
with #__next
as its id
attribute:
<html>
<body>
<div id="__next">
<div id="map"></div>
</div>
</body>
</html>
By default, the #__next
container has height:auto
. As it doesn't recognize any content, the height will be zero. So the following CSS declaration
#map {
height: 100%;
}
will set the height of the #map
container to be 100% of zero. That is, zero. As a result, a Google Map inside the container won't be shown.
A workaround is suggested by SkyzohKey (2018):
#__next {
height: 100%;
}
This will ensure that the #__next
container's height will be 100% of the body
element's height, which is in turn 100% of the html
element's height, which is in turn 100% of the browser window height.
- Incidentally, I haven't found any documentation saying the
height:100%
will refer to the browser window height when it's applied to thehtml
element. Let me know if you know where to look for.
Consequently, the #map
container's height will be 100% of the #__next
container, that is, the browser window height.
Gotcha #2: React hooks
TL;DR
Compose the pages/index.js
as follows:
// pages/index.js
import {useEffect, useRef} from 'react';
import {Loader} from '@googlemaps/js-api-loader';
function HomePage() {
const googlemap = useRef(null);
useEffect(() => {
const loader = new Loader({
apiKey: 'yourAPIkey',
version: 'weekly',
});
let map;
loader.load().then(() => {
map = new google.maps.Map(googlemap.current, {
center: {lat: -34.397, lng: 150.644},
zoom: 8,
});
});
});
return (
<div id="map" ref={googlemap} />
);
}
export default HomePage;
Detail
Google (2021) suggests the following JavaScript code to embed a Google Map:
map = new google.maps.Map(document.getElementById("map"), {
center: { lat: -34.397, lng: 150.644 },
zoom: 8,
});
where the #map
container is referenced with document.getElementById("map")
. An experienced React user can immediately tell that this should be replaced with the useRef
hook.
- For why we should use
useRef()
instead ofdocument.getElementById()
, see Farmer (2018).
In addition, when we need to refer to the element during the initial rendering of a React component, we should use the useEffect
hook. So all the JavaScript code to embed a Google Map needs to be written inside the useEffect
hook block.
This is a technique I've learned for using the canvas
element with React. See Kudamatsu (2020) (see Step 4) for detail.
Gotcha #3: Handling ESLint error
TL;DR
Add the following line immediately before creating a map instance:
const google = window.google;
Detail
The code in the previous two sections will do render a Google Map. But if you use ESLint, it throws an error because of this line:
map = new google.maps.Map(googlemap.current, {...});
The object called google
is used without being defined. ESLint doesn't like it. And it is a compile error. So you cannot tell ESLint to ignore this line of code (ESLint 2019).
A workaround is suggested by Abramov (2017). He explains why ESLint complains:
"... people commonly misunderstand the difference between local variables, imported modules, and global variables, and so we want to always make it clear in the code when you use a global variable."
So to make it clear that google
is a global variable, we should write the useEffect
block of code in the following way:
useEffect(() => {
const loader = new Loader({
apiKey: 'yourAPIkey',
version: 'weekly',
});
let map;
loader.load().then(() => {
const google = window.google; // ADDED
map = new google.maps.Map(googlemap.current, {
center: {lat: -34.397, lng: 150.644},
zoom: 8,
});
});
});
The window.google
is undefined
until the Google API library is referenced (Marcus 2018). So it has to be inside the loader.load().then()
block.
Summary
Your pages/index.js
should look like this:
// pages/index.js
import {useEffect, useRef} from 'react';
import {Loader} from '@googlemaps/js-api-loader';
function HomePage() {
const googlemap = useRef(null);
useEffect(() => {
const loader = new Loader({
apiKey: 'yourAPIkey',
version: 'weekly',
});
let map;
loader.load().then(() => {
const google = window.google;
map = new google.maps.Map(googlemap.current, {
center: {lat: -34.397, lng: 150.644},
zoom: 8,
});
});
});
return (
<div id="map" ref={googlemap} />
);
}
export default HomePage;
Then add the following CSS declarations:
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#__next {
height: 100%;
}
#map {
height: 100%;
}
Hope this helps!
References
Dan Abramov (2017) “An answer to ‘google is not defined in react app using create-react-app’”, Stack Overflow, May 1, 2017.
ESLint (2019) “Disabling Rules with Inline Comments”, ESLint User Guide, Dec. 1, 2019.
Farmer, Andrew H. (2018) “Why to use refs instead of IDs”, JavaScript Stuff, Jan 27, 2018.
Google (2021) “Overview”, Maps JavaScript API Guides, Feb. 10, 2021.
Kudamatsu, Masa (2020) “How to use HTML Canvas with React Hooks — Web Dev Survey from Kyoto”, medium.com, Dec. 9, 2020.
Marcus, Scott (2018) “A comment to ‘window.google is undefined in react?’”, Stack Overflow, Apr. 25, 2018.
SkyzohKey (2018) “An answer to ‘Nextjs:How to change css of root div __next on specific page?’”, Stack Overflow, Dec. 5, 2018.
Discussion (0)