El propósito de esta publicación es enseñar como utilizar la librería de MapBox GL JS para mostrar mapas interactivos en aplicaciones de React JS.
En este caso vamos a mostrar un mapa, y agregarle un evento, que se ejecute al momento de dar doble click se coloque un marcador en esa posición a la que se acaba de dar doble click.
🚨 Nota: Este post requiere que sepas las bases de React con TypeScript (hooks básicos y peticiones con fetch).
Cualquier tipo de Feedback o mejora es bienvenido, gracias y espero disfrutes el articulo. 🤗
▶️ CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)
🧵 Antes de empezar a codear ...
Antes de empezar a trabajar con el código tenemos que hacer un par de cosas para poder utilizar el mapa de MapBox.
1- Tienes que crear una cuenta en MapBox.
2- En tu cuenta buscaras el access token que te crea MapBox por defecto o si lo prefieres, puedes crear un nuevo token de acceso.
3- Guardar ese token de acceso para usarlo después.
🧵 Creando el proyecto.
Al proyecto le colocaremos el nombre de: show-mapbox (opcional, tu le puedes poner el nombre que gustes).
npm init vite@latest
Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.
cd show-mapbox
Luego instalamos las dependencias.
npm install
Después abrimos el proyecto en un editor de código (en mi caso VS code).
code .
🧵 Primeros pasos.
Necesitamos instalar MapBox en nuestra aplicación:
npm i mapbox-gl
Y como estamos usando TypeScript, necesitamos instalar los tipos de MapBox:
npm i -D @types/mapbox-gl
Dentro de la carpeta src/App.tsx borramos todo el contenido del archivo y colocamos un h1 que diga "Hello world" por mientras.
Creamos la carpeta src/components y creamos el archivo MapView.tsx
Y lo único que necesitamos para mostrar el mapa es una etiqueta div
🚨 Nota: Antes de empezar a mostrar el mapa; a este componente se le debe dar estilos, un alto y un ancho para que luego podamos visualizar el mapa correctamente.
Para mostrar el mapa necesitaremos usar 2 hooks
El primero sera el useRef. Necesitamos useRef para guardar la referencia del div donde se va a renderizar el mapa.
El otro hook es el useEffect. Usaremos este hook para inicializar el mapa.
🟠 Manteniendo la referencia al contenedor del mapa.
Usamos el hook useRef para esta tarea, de la siguiente manera:
Bueno, podríamos solo colocar solo un ID al div y ya con eso funcionaria. 😌
El problema sera cuando queramos usar mas de un mapa. 🤔
Si usamos más de un componente MapView, solo se renderizaría un solo mapa por que tienen el mismo ID; y para evitar eso, usamos el hook useRef, ya que cada vez que reutilizamos el componente MapView se creara una nueva referencia.
🟠 Inicializando MapBox.
Creamos la carpeta src/utils y creamos un nuevo archivo llamado initMap.ts y ahí construiremos la función para inicializar el mapa.
Esta función tiene que recibir:
container: elemento HTML, en este caso el div, donde se renderizará el mapa.
coords: coordenadas del lugar. Tienen que ser de tipo array de dos números, donde la primera posición es la longitud y la segunda posición es la latitud.
Dentro de la función vamos a retornar una nueva instancia de Map.
La retornamos ya que vamos a necesitar esa instancia para hacer mas eventos y acciones. En el caso de que solo ocupes mostrar el mapa y ya, no sera necesario retornar nada.
pitchWithRotate: es el control de inclinación del mapa, que en este caso lo queremos quitar, por eso colocamos false.
center: son las coordenadas donde se posicionará el mapa al inicializarse, su valor serán las coords que nos llega por parámetro de la función.
zoom: el zoom inicial del mapa, los niveles van de 0 a 22.
accessToken: el token que guardamos con anterioridad. Por lo que te recomiendo guardar este token en una variable de entorno y usar dicha variable en esta propiedad de accesToken.
doubleClickZoom: acción que se dispara al hacer doble click por defecto es el aumentar el zoom, pero lo pondremos el false, ya que usaremos la acción del doble click para otra tarea.
Y asi quedaría nuestra función lista para usarla. 😌
Ahora de vuela en nuestro componente MapView usaremos el useEffect para llamar la función que hemos creado.
Dentro del useEffect haremos una condición, donde solo si existe el valor de useRef, inicializaremos nuestro mapa.
En la función initMap, mandamos el elemento HTML que se encuentra en la propiedad current de mapRef,
después mandamos las coordenadas ( [longitud, latitud] ).
En este punto, sera mejor refactorizar nuestro código, creando un custom hook para manejar la lógica del mapa y dejar limpio nuestro componente MapView.
Creamos la carpeta src/hooks y dentro creamos el archivo useMap.ts y movemos la lógica del MapView al archivo useMap.ts.
Este custom hook recibe como parámetro el contenedor donde se renderizará el mapa.
Ahora, remplazamos la palabra mapRef por container.
Bueno, hasta ahora ya tenemos la referencia a la instancia del mapa a disposición.
Ahora lo que queremos hacer es que al cargar el mapa, se muestre un marcador en pantalla.
Para esto, la instancia de Map tiene el método 'on' que nos permite escuchar ciertos eventos que se disparan en el mapa.
Asi que, primero creamos un useEffect.
useEffect(()=>{},[])
Luego, vamos hacer una evaluación donde si el mapInitRef.current existe (o sea que tiene el valor de la instancia),
ejecutamos el siguiente evento 'on()'.
Un extra, seria crearle un PopUp. Para ello hacemos una nueva instancia de la clase Popup (la guardamos en una constante), la cual le mandamos ciertos parámetros que son opcionales:
closeButton: mostrar el botón de cerrar, lo colocamos el falso.
anchor: la posición donde debe mostrase el PopUp en el marcador.
En nuestro hook useMap, dentro del useEffect donde estábamos creando agregando el evento para escuchar el mapa cuando cargue por primera vez, hacemos la llamada del método generateNewMarker.
map: le mapInitRef.current ya que es la instancia del mapa.
el segundo parámetro mandamos mapInitRef.current!.getCenter().
Esta función retorna un array de dos números que son la longitud y latitud (estos números son los que le pasamos al principio, al momento de inicializar el mapa), por lo cual los esparcimos con el operador spread.
Por ultimo, es buena practica que cuando estamos escuchando eventos dentro de un useEffect, al momento de que se desmonte el componente (que en este caso no va a pasar ya que solo tenemos una vista que es la del mapa), es necesario dejar de escuchar el evento y no ejecutar nada.
🧵 Agregar un nuevo marcador en el mapa cuando se haga doble click.
Esto sera muy sencillo, ya que tenemos casi todo hecho.
Solo es necesario, agregar un nuevo efecto en nuestro custom hook.
Y siguiendo las mismas practicas que cuando escuchamos el evento 'load' anteriormente.
Hacemos la validación de que mapInitRef contenga la instancia del mapa.
Llamamos al método on para escuchar el evento de 'dblclick'.
Ahora, el listener que se ejecuta nos de acceso a la longitud y latitud (que vienen como un array de dos números), las cuales podemos desestructurar del listener.
Ejecutamos la función generateNewMarker.
A la función generateNewMarker le mandamos el map, que tendrá el valor de la instancia del mapa que se encuentra en mapInitRef.current. Después, le esparcimos el valor de lngLat que nos otorga el listener.
Limpiamos el efecto con el return, dejando de escuchar el evento 'dblclick'