Cómo renderizar un mapa con marcadores usando Google Maps en Next.js

Cómo representar un mapa de Google con marcadores dentro de un componente React usando Next.js y animando ese mapa en función de un límite de marcador.

Primeros pasos

Para este tutorial, vamos a utilizar CheatCode Next.js Boilerplate como punto de partida para nuestro trabajo. Primero, clonemos una copia:

Terminal

git clone https://github.com/cheatcode/nextjs-boilerplate

A continuación, necesitamos instalar las dependencias para el repetitivo:

Terminal

cd nextjs-boilerplate && npm install

Finalmente, inicie el repetitivo:

Terminal

npm run dev

Con eso, estamos listos para comenzar.

Adición de Google Maps a través de CDN

Antes de implementar nuestro mapa, vamos a necesitar acceso a la API de JavaScript de Google Maps. Para obtener acceso, vamos a utilizar el enlace CDN oficial de Google para la API:

/pages/_document.js

import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheet } from "styled-components";
import settings from "../settings";

export default class extends Document {
  static async getInitialProps(ctx) { ... }

  render() {
    const { styles } = this.props;

    return (
      <Html lang="en">
        <Head>
          <meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
          <meta name="application-name" content="App" />
          ...
          <script
            src={`https://maps.googleapis.com/maps/api/js?key=${settings?.googleMaps?.apiKey}&callback=initMap&libraries=&v=weekly`}
            async
          ></script>
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Arriba, en el /pages/_document.js archivo que está incluido en el repetitivo, en el <Head></Head> etiqueta, hemos pegado en el <script></script> etiqueta que Google recomienda para incluir la API JavaScript de Google Maps en una página web.

Debido a que este archivo es bastante grande, hemos condensado algunas de las otras etiquetas en el <Head></Head> etiqueta con ... . El lugar donde quieres colocar tu propio <script></script> la etiqueta está justo antes del cierre </Head> etiqueta.

De nota, aquí, hemos cambiado el src atributo en la etiqueta que obtenemos de Google para permitirnos usar la interpolación de cadenas para que podamos pasar nuestra clave API de Google Maps a través de nuestro archivo de configuración. En el modelo que estamos usando, el /settings/index.js es responsable de cargar automáticamente el contenido del /settings/settings-<env>.js apropiado donde el <env> parte es igual al valor actual de process.env.NODE_ENV o el entorno actual en el que se ejecuta la aplicación (para este tutorial, development o settings-development.js ).

Si aún no tiene una clave API de Google Maps, obtenga información sobre cómo crear una aquí antes de continuar.

De vuelta en nuestro /pages/_document.js archivo, podemos importar settings de /settings/index.js y haga referencia a los valores en nuestro settings-<env>.js expediente. Aquí, esperamos que el archivo contenga un objeto con un googleMaps propiedad y un apiKey anidado valor, así:

/configuración/configuración-desarrollo.js

const settings = {
  googleMaps: {
    apiKey: "Paste Your API Key Here",
  },
  graphql: {
    uri: "http://localhost:5001/api/graphql",
  },
  ...
};

export default settings;

Con todo eso configurado, ahora, cuando cargamos nuestra aplicación, tendremos un google global valor disponible que tendrá un .maps objeto que usaremos para interactuar con la biblioteca.

Configuración de estilos de mapas globales

Con la API de Google Maps cargada, a continuación, queremos crear nuestra aplicación. Muy rápido antes de que lo hagamos, para nuestra demostración, queremos agregar un estilo CSS global a nuestra aplicación que mostrará nuestro mapa a pantalla completa en la aplicación:

/pages/_app.js

...
import { createGlobalStyle } from "styled-components";
...

const GlobalStyle = createGlobalStyle`
  :root {
    ...
  }

  ${pong} /* CSS for /lib/pong.js alerts. */

  body > #__next > .container {
    padding-top: 20px;
    padding-bottom: 20px;
  }

  body.is-map > #__next > .navbar {
    display: none;
  }

  body.is-map > #__next > .container {
    width: 100%;
    max-width: 100%;
    padding: 0 !important;
  }

  ...
`;

class App extends React.Component {
  state = {
    loading: true,
  };

  async componentDidMount() { ... }

  render() { ... }
}

App.propTypes = {
  Component: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired,
  pageProps: PropTypes.object.isRequired,
};

export default App;

En la cadena pasada a createGlobalStyle (indicado por los acentos graves `` ), estamos agregando dos reglas CSS, anticipando que se aplicará una clase a nuestro <body></body> etiqueta is-map :

body.is-map > #__next > .navbar {
  display: none;
}

body.is-map > #__next > .container {
  width: 100%;
  max-width: 100%;
  padding: 0 !important;
}

La primera regla aquí es seleccionar el elemento de la barra de navegación incluido en el modelo predeterminado y ocultarlo de la pantalla si el <body></body> la etiqueta tiene el .is-map clase. La segunda regla:también dirigida a .is-map clase:localiza el <div className="container"></div> elemento que envuelve el contenido principal de la página más abajo en el render() función en el archivo. Los estilos aquí obligan a ese contenedor a llenar todo el ancho de la página y elimina su relleno predeterminado (asegurando que no haya espacio en los lados izquierdo y derecho del mapa).

Creando nuestro mapa

Ahora estamos listos para configurar nuestro mapa. Debido a que estamos usando Next.js en el modelo que clonamos anteriormente, vamos a confiar en el enrutador de ese marco que usa el /pages directorio para crear rutas para nuestra aplicación. Para nuestra demostración, representaremos nuestro mapa en http://localhost:5000/map , por lo que queremos crear una nueva carpeta llamada map en /pages :

/páginas/mapa/index.js

import React from "react";
import StyledMap from "./index.css";

class Map extends React.Component {
  state = {};

  componentDidMount() {
    document.body.classList.add("is-map");
  }

  componentWillUnmount() {
    document.body.classList.remove("is-map");
  }

  render() {
    return (
      <StyledMap>
        <div id="google-map" />
      </StyledMap>
    );
  }
}

Map.propTypes = {
  // prop: PropTypes.string.isRequired,
};

export default Map;

Aquí, estamos creando un componente de React basado en clases, una forma mucho más fácil de implementar Google Maps en React en lugar de usar el patrón de componente de función. Abajo en el render() método, renderizamos un componente <StyledMap></StyledMap> que envuelve un <div></div> vacío con el an id de google-map (donde mostraremos nuestro mapa).

En el componentDidMount() función, observe que estamos configurando el is-map clase en el <body></body> etiqueta como insinuamos anteriormente y en el componentWillUnmount() función (llamada cuando nos alejamos del /map página), nos aseguramos de eliminar el is-map class ya que esta es la única página en la que queremos que se utilicen los estilos que aplicamos en función de ese nombre de clase.

Muy rápido, abramos ese StyledMap componente que estamos importando desde ./index.css cerca de la parte superior de nuestro archivo:

/páginas/mapa/index.css.js

import styled from "styled-components";

export default styled.div`
  #google-map {
    width: 100%;
    height: 100vh;
  }
`;

Muy simple. Aquí, estamos usando el styled-components biblioteca que se incluye en el Boilerplate de Next.js que estamos usando para crear un componente React al que se le aplicará automáticamente algo de CSS. Aquí llamamos al styled.div función incluida en la biblioteca y pasarle una cadena (indicada por el `` comillas invertidas aquí) de CSS que queremos aplicar a un componente React que devuelve un <div></div> etiqueta.

En caso de que la sintaxis parezca extraña, el styled.div`` es solo una abreviatura de styled.div(``) (JavaScript nos permite omitir los paréntesis si el único argumento que estamos pasando a la función es una cadena).

Para nuestros estilos, solo le decimos el <div></div> donde inyectaremos nuestro Google Map para llenar todo el ancho y alto de la pantalla.

/páginas/mapa/index.js

import React from "react";
import StyledMap from "./index.css";

class Map extends React.Component {
  state = {
    defaultCenter: {
      lat: 36.1774465,
      lng: -86.7042552,
    },
  };

  componentDidMount() {
    document.body.classList.add("is-map");
    this.handleAttachGoogleMap();
  }

  componentWillUnmount() { ... }

  handleAttachGoogleMap = () => {
    const { defaultCenter } = this.state;
    this.map = new google.maps.Map(document.getElementById("google-map"), {
      center: defaultCenter,
      zoom: 10,
    });
  };

  render() {
    return (
      <StyledMap>
        <div id="google-map" />
      </StyledMap>
    );
  }
}

Map.propTypes = {
  // prop: PropTypes.string.isRequired,
};

export default Map;

A continuación, en nuestro componentDidMount() , hemos agregado una llamada a una nueva función handleAttachGoogleMap() donde hemos agregado la parte importante:una llamada a new google.maps.Map() pasando una llamada a document.getElementById('google-map') como primer argumento y luego un objeto JavaScript con algunas configuraciones para nuestro mapa.

Esto está diciendo "seleccione el <div id="google-map" /> elemento abajo en nuestro render() y representar el mapa de Google en ese lugar". Para las opciones, configuramos el center propiedad (donde se colocará el centro del mapa cuando se carga) a algunas coordenadas que hemos configurado en el state valor bajo defaultCenter . Tenga en cuenta que Google espera que pasemos coordenadas como pares de latitud y longitud a través de objetos JavaScript con lat y lng como propiedades que contienen esos valores.

Para el zoom configuramos esto en el nivel 10 (cuanto mayor sea el valor de zoom, cuanto más nos acerquemos al nivel de la calle, cuanto menor sea el valor de zoom, más nos alejaremos). Finalmente, asignamos el resultado de llamar a new google.maps.Map() a this.map . Lo que esto nos ayuda a lograr es hacer que nuestra instancia de Google Maps sea accesible para todo nuestro componente. Esto será útil cuando veamos cómo agregar marcadores a nuestro mapa.

Agregando marcadores a nuestro mapa

Ahora que tenemos acceso a una instancia de Google Maps, podemos agregar algunos marcadores al mapa. Para acelerar las cosas, agregaremos una matriz de markers al state predeterminado valor cerca de la parte superior de nuestro componente con algunos lugares cerca de nuestro defaultCenter (puede cambiarlos para que se ajusten a las necesidades de su propio mapa):

/páginas/mapa/index.js

import React from "react";
import StyledMap from "./index.css";

class Map extends React.Component {
  state = {
    defaultCenter: {
      lat: 36.1774465,
      lng: -86.7042552,
    },
    markers: [
      {
        lat: 36.157055,
        lng: -86.7696144,
      },
      {
        lat: 36.1521981,
        lng: -86.7801724,
      },
      {
        lat: 36.1577547,
        lng: -86.7785841,
      },
      {
        lat: 36.1400674,
        lng: -86.8382887,
      },
      {
        lat: 36.1059131,
        lng: -86.7906082,
      },
    ],
  };

  componentDidMount() { ... }

  componentWillUnmount() { ... }

  handleAttachGoogleMap = () => {
    const { defaultCenter } = this.state;
    this.map = new google.maps.Map(...);

    setTimeout(() => {
      this.handleDrawMarkers();
    }, 2000);
  };

  handleDrawMarkers = () => {
    const { markers } = this.state;
    markers.forEach((marker) => {
      new google.maps.Marker({
        position: marker,
        map: this.map,
      });
    });
  };

  render() { ... }
}

Map.propTypes = {
  // prop: PropTypes.string.isRequired,
};

export default Map;

Dentro de handleAttachGoogleMap , después de haber creado nuestra instancia de mapa, ahora agregaremos una llamada a this.handleDrawMarkers() , una función que estamos agregando donde representaremos los marcadores para nuestro mapa. Cabe destacar que, para que nuestra demostración sea más pulida, estamos envolviendo un setTimeout() durante dos segundos para decir "cargue el mapa y luego, después de dos segundos, dibuje los marcadores". Esto hace que la experiencia de carga sea visualmente más interesante para los usuarios (aunque no es obligatorio, así que siéntete libre de eliminarlo).

Dentro de handleDrawMarkers() , usamos la desestructuración de JavaScript para "arrancar" el markers valor que hemos agregado a state (nuevamente, solo una matriz de objetos de latitud/longitud). Uso de JavaScript .forEach() método en nuestro markers matriz, recorremos la lista y para cada uno, llamamos a new google.maps.Markers() . A esa función, le pasamos un objeto de opciones que describe el position para nuestro marcador (el par latitud/longitud) y el map queremos agregar el marcador a (nuestra instancia existente de Google Map que almacenamos en this.map ).

Aunque puede no parecer mucho, cuando cargamos nuestra página, deberíamos ver nuestro mapa renderizado y, después de un retraso de dos segundos, aparecen nuestros marcadores.

Sin embargo, aún no hemos terminado. Para terminar, vamos a pulir las cosas usando Google Maps bounds característica para limpiar la experiencia del usuario.

Uso de marcadores como límites del mapa para establecer el centro y el zoom

Todo el trabajo que tenemos que hacer ahora estará en el handleDrawMarkers() función:

/pages/maps/index.js

handleDrawMarkers = () => {
  const { markers } = this.state;
  const bounds = new google.maps.LatLngBounds();

  markers.forEach((marker) => {
    new google.maps.Marker({
      position: marker,
      map: this.map,
    });

    bounds.extend(marker);
  });

  this.map.fitBounds(bounds);
  this.map.panToBounds(bounds);
};

Centrándonos solo en esa función, ahora queremos usar el .LatLngBounds() método en el google.maps biblioteca para ayudarnos a establecer un límite alrededor de nuestros marcadores en el mapa. Para hacerlo, agregamos una línea arriba de nuestro .forEach() , creando una instancia de google.maps.LatLngBounds() , almacenándolo en una variable const bounds .

A continuación, dentro de nuestro markers.forEach() , después de haber creado nuestro marcador, agregamos una llamada a bounds.extend() , pasando nuestro marker (nuestro par latitud/longitud). Esta función "empuja" el límite que inicializamos en bounds para incluir el marcador que estamos recorriendo actualmente (piense en esto como empujar la masa de pizza en un círculo en su mostrador donde el centro de la pizza es donde se encuentran nuestros marcadores).

Debajo de nuestro .forEach() bucle, a continuación llamamos a dos funciones en nuestro this.map instancia:.fitBounds() que toma el bounds hemos construido y "reducido" el mapa a ese límite (acercamiento) y .panToBounds() , mueve el centro del mapa para que sea el centro del límite que acabamos de dibujar.

Con esto, ahora, cuando cargue nuestro mapa, veremos una buena animación a medida que nuestros marcadores se agreguen al mapa.

Terminando

En este tutorial, aprendimos cómo agregar Google Maps a una aplicación Next.js y representar un mapa en un componente React.js, completo con marcadores y un efecto de zoom animado basado en el límite de esos marcadores.