Search code examples
reactjsgoogle-mapsbabeljs

How to use google map with advanced markers in react?


I was trying to create a custom marker using Google Maps advanced markers

Data so that you can reproduce the example. Just put any image where image is being used.

 const center = {
    lat: 28.7041,
    lng: 77.102,
  };

  const markers = [
    {
      key: "1",
      coordinates: {
        lat: 28.7041,
        lng: 77.102,
      },
      marker: {
        text: "BUS-001",
        backgroundColor: "black",
      },
    },
    {
      key: "2",
      coordinates: {
        lat: 28.4557,
        lng: 77.032,
      },
      marker: {
        text: "BUS-002",
        backgroundColor: "red",
      },
    },
    {
      key: "3",
      coordinates: {
        lat: 28.457,
        lng: 77.0412,
      },
      marker: {
        text: "BUS-003",
        backgroundColor: "blue",
      },
    },
  ];
 
import React, { useEffect, useState } from "react";
import Typography from "@mui/material/Typography"; // Update the import statement
import busTopViewImage from "../assets/bus-top-view.png";
import { Grid } from "@mui/material";


const Map = ({ center, markers }) => {
  useEffect(() => {
    async function initMap() {
      const { Map } = (await google.maps.importLibrary(
        "maps"
      )) as google.maps.MapsLibrary;
      const { AdvancedMarkerElement } = (await google.maps.importLibrary(
        "marker"
      )) as google.maps.MarkerLibrary;

      const map = new Map(document.getElementById("map-view") as HTMLElement, {
        center,
        zoom: 14,
        mapId: "4504f8b37365c3d0",
      });
      let mapMarkers = [];
      for (let markerData of markers) {
        const markerComponent = document.createElement("div");
        markerComponent.innerHTML = `
  <img src="${busTopViewImage}" />
  <h5 style="background-color: ${markerData.marker.backgroundColor}; color: white; border-radius: 10px; padding: 8px;">
        ${markerData.marker.text}
  </h5>
`;
        const m = new AdvancedMarkerElement({
          map,
          position: markerData.coordinates,
          content: markerComponent,
        });
        mapMarkers.push(m);
      }
    }
    initMap();
  }, []);

  return <div id="map-view" style={{ width: "100%", height: "100%" }}></div>;
};
export default Map;

The issue I am facing is that I want to use a React component as marker as below.

const Marker = ({ text, backgroundColor }) => {
  return (
    <Grid
      container
      justifyContent="stretch"
      alignItems="center"
      direction="column"
      style={{
        boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)",
        marginBottom: "3rem",
      }}
    >
      <img src={busTopViewImage} alt="Bus Marker" />
      <Grid
        item
        style={{
          boxShadow: "0 2px 4px rgba(0, 0, 0, 0.2)",
          backgroundColor: backgroundColor,
          borderRadius: "10px",
          padding: "0 0.5rem",
        }}
      >
        <Typography variant="body1" color="white">
          {text}
        </Typography>
      </Grid>
    </Grid>
  );
};

To what I know about react, it uses babel to convert jsx to javascript nodes using the document.createElement as that api wants. I want to know if there is any library/react function that can ingest this Marker component and help me use remove that ugly markerComponent.innerHTML in that useEffect. If you know of any method please share an example or provide some sources of information.

I already tried the react packages available for google maps. Some only allow static images and one of them accepted the Marker but while zooming in and out didn't change the marker position.


Solution

  • As you can tell, you can pass DOM nodes into advanced markers like you have here:

            const markerComponent = document.createElement("div");
            markerComponent.innerHTML = `
      <img src="${busTopViewImage}" />
      <h5 style="background-color: ${markerData.marker.backgroundColor}; color: white; border-radius: 10px; padding: 8px;">
            ${markerData.marker.text}
      </h5>
    `;
            const m = new AdvancedMarkerElement({
              map,
              position: markerData.coordinates,
              content: markerComponent,
            });
    

    The only thing you're missing is to generate the HTML via React.

        // Setup a React root on a virtual DOM node
        const markerComponent = document.createElement("div");
        // Initialize root at new DOM node
        const root = ReactDOM.createRoot(el);
        // Output HTML into it
        root.render(<Marker ... />);
    
         // Use it
        const m = new AdvancedMarkerElement({
              map,
              position: markerData.coordinates,
              content: markerComponent,
            });
    

    If you need to share state with the rest of the tree, you can combine this with Portals.