Ajouter une carte et des repères à une application React

Présentation

Ce tutoriel explique comment ajouter une carte et un repère à une application React à l'aide de @googlemaps/react-wrapper, et comment intégrer la carte et les repères à l'état de l'application.

Installer @googlemaps/react-wrapper

Installez et utilisez la bibliothèque @googlemaps/react-wrapper pour charger dynamiquement l'API Maps JavaScript lorsque le composant est affiché.

npm install @googlemaps/react-wrapper

Cette bibliothèque peut être importée et utilisée avec les éléments suivants :

import { Wrapper, Status } from "@googlemaps/react-wrapper";

Ce composant est essentiellement utilisé pour encapsuler des composants enfants qui dépendent de l'API Maps JavaScript. Le composant Wrapper accepte également une propriété render pour afficher les composants de chargement ou pour gérer les erreurs de chargement de l'API Maps JavaScript.

const render = (status: Status) => {
  return <h1>{status}</h1>;
};

<Wrapper apiKey={"YOUR_API_KEY"} render={render}>
  <YourComponent/>
</Wrapper>

Ajouter un composant de carte

Un composant fonctionnel de base permettant d'afficher une carte utilisera probablement les hooks React useRef, useState et useEffect.

Le composant de carte initial comportera la signature suivante :

const Map: React.FC<{}> = () => {};

Étant donné que google.maps.Map nécessite un Element en tant que paramètre constructeur, vous devez utiliser useRef pour conserver un objet modifiable qui sera conservé pendant la durée de vie du composant. L'extrait de code suivant instancie une carte au sein du hook useEffect dans le corps du composant Map.

TypeScript

const ref = React.useRef<HTMLDivElement>(null);
const [map, setMap] = React.useState<google.maps.Map>();

React.useEffect(() => {
  if (ref.current && !map) {
    setMap(new window.google.maps.Map(ref.current, {}));
  }
}, [ref, map]);

JavaScript

const ref = React.useRef(null);
const [map, setMap] = React.useState();

React.useEffect(() => {
  if (ref.current && !map) {
    setMap(new window.google.maps.Map(ref.current, {}));
  }
}, [ref, map]);

Le hook useEffect ci-dessus ne s'exécutera que lorsque ref aura changé. Le composant Map renvoie désormais les éléments suivants :

return <div ref={ref} />

Étendre le composant de carte avec des accessoires supplémentaires

Le composant de carte de base peut être étendu en ajoutant des accessoires pour les options de carte, les écouteurs d'événements et les styles appliqués à l'élément div contenant la carte. Le code suivant présente l'interface développée de ce composant fonctionnel.

interface MapProps extends google.maps.MapOptions {
  style: { [key: string]: string };
  onClick?: (e: google.maps.MapMouseEvent) => void;
  onIdle?: (map: google.maps.Map) => void;
}

const Map: React.FC<MapProps> = ({
  onClick,
  onIdle,
  children,
  style,
  ...options
}) => {}

L'objet style peut être transmis directement et défini comme accessoire sur le div affiché.

return <div ref={ref} style={style} />;

onClick, onIdle et google.maps.MapOptions nécessitent des hooks useEffect pour appliquer systématiquement les modifications à google.maps.Map.

TypeScript

// because React does not do deep comparisons, a custom hook is used
// see discussion in https://github.com/googlemaps/js-samples/issues/946
useDeepCompareEffectForMaps(() => {
  if (map) {
    map.setOptions(options);
  }
}, [map, options]);

JavaScript

// because React does not do deep comparisons, a custom hook is used
// see discussion in https://github.com/googlemaps/js-samples/issues/946
useDeepCompareEffectForMaps(() => {
  if (map) {
    map.setOptions(options);
  }
}, [map, options]);

Les écouteurs d'événements nécessitent un code légèrement plus complexe pour effacer les écouteurs existants lorsqu'un gestionnaire transmis en tant qu'accessoire a été mis à jour.

TypeScript

React.useEffect(() => {
  if (map) {
    ["click", "idle"].forEach((eventName) =>
      google.maps.event.clearListeners(map, eventName)
    );

    if (onClick) {
      map.addListener("click", onClick);
    }

    if (onIdle) {
      map.addListener("idle", () => onIdle(map));
    }
  }
}, [map, onClick, onIdle]);

JavaScript

React.useEffect(() => {
  if (map) {
    ["click", "idle"].forEach((eventName) =>
      google.maps.event.clearListeners(map, eventName)
    );
    if (onClick) {
      map.addListener("click", onClick);
    }

    if (onIdle) {
      map.addListener("idle", () => onIdle(map));
    }
  }
}, [map, onClick, onIdle]);

Créer un composant de repère

Le composant de repère utilise des modèles semblables à ceux du composant de carte, avec des hooks useEffect et useState.

TypeScript

const Marker: React.FC<google.maps.MarkerOptions> = (options) => {
  const [marker, setMarker] = React.useState<google.maps.Marker>();

  React.useEffect(() => {
    if (!marker) {
      setMarker(new google.maps.Marker());
    }

    // remove marker from map on unmount
    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);

  React.useEffect(() => {
    if (marker) {
      marker.setOptions(options);
    }
  }, [marker, options]);

  return null;
};

JavaScript

const Marker = (options) => {
  const [marker, setMarker] = React.useState();

  React.useEffect(() => {
    if (!marker) {
      setMarker(new google.maps.Marker());
    }

    // remove marker from map on unmount
    return () => {
      if (marker) {
        marker.setMap(null);
      }
    };
  }, [marker]);
  React.useEffect(() => {
    if (marker) {
      marker.setOptions(options);
    }
  }, [marker, options]);
  return null;
};

Le composant renvoie "null", car google.maps.Map gère la manipulation DOM.

Ajouter des repères en tant que composant enfant de la carte

Pour ajouter des repères à une carte, le composant Marker est transmis au composant Map à l'aide de l'accessoire children spécial, comme suit :

<Wrapper apiKey={"YOUR_API_KEY"}>
  <Map center={center} zoom={zoom}>
    <Marker position={position} />
  </Map>
</Wrapper>

Une légère modification de la sortie du composant Map doit être effectuée pour transmettre l'objet google.maps.Map à tous les enfants en tant qu'accessoire supplémentaire.

TypeScript

return (
  <>
    <div ref={ref} style={style} />
    {React.Children.map(children, (child) => {
      if (React.isValidElement(child)) {
        // set the map prop on the child component
        // @ts-ignore
        return React.cloneElement(child, { map });
      }
    })}
  </>
);

JavaScript

return (
  <>
    <div ref={ref} style={style} />
    {React.Children.map(children, (child) => {
      if (React.isValidElement(child)) {
        // set the map prop on the child component
        // @ts-ignore
        return React.cloneElement(child, { map });
      }
    })}
  </>
);

Associer la carte à l'état de l'application

En utilisant le modèle ci-dessus pour les rappels onClick et onIdle, l'application peut être étendue pour intégrer complètement des actions de l'utilisateur, comme un clic ou un panoramique sur la carte.

TypeScript

const [clicks, setClicks] = React.useState<google.maps.LatLng[]>([]);
const [zoom, setZoom] = React.useState(3); // initial zoom
const [center, setCenter] = React.useState<google.maps.LatLngLiteral>({
  lat: 0,
  lng: 0,
});

const onClick = (e: google.maps.MapMouseEvent) => {
  // avoid directly mutating state
  setClicks([...clicks, e.latLng!]);
};

const onIdle = (m: google.maps.Map) => {
  console.log("onIdle");
  setZoom(m.getZoom()!);
  setCenter(m.getCenter()!.toJSON());
};

JavaScript

const [clicks, setClicks] = React.useState([]);
const [zoom, setZoom] = React.useState(3); // initial zoom
const [center, setCenter] = React.useState({
  lat: 0,
  lng: 0,
});

const onClick = (e) => {
  // avoid directly mutating state
  setClicks([...clicks, e.latLng]);
};

const onIdle = (m) => {
  console.log("onIdle");
  setZoom(m.getZoom());
  setCenter(m.getCenter().toJSON());
};

Ces hooks peuvent être intégrés aux éléments du formulaire selon le format suivant, comme illustré dans l'entrée de latitude.

<label htmlFor="lat">Latitude</label>
<input
  type="number"
  id="lat"
  name="lat"
  value={center.lat}
  onChange={(event) =>
    setCenter({ ...center, lat: Number(event.target.value) })
  }
/>

Enfin, l'application peut suivre les clics et afficher des repères à chaque emplacement de clic.

{clicks.map((latLng, i) => (<Marker key={i} position={latLng} />))}

Explorer le code

Vous pouvez explorer l'exemple de code complet dans les bacs à sable en ligne ci-dessous ou en clonant le dépôt Git.

Essayer l'exemple

Cloner l'échantillon

Git et Node.js sont requis pour exécuter cet exemple en local. Suivez ces instructions pour installer Node.js et NPM. Les commandes suivantes clonent, installent des dépendances et démarrent l'exemple d'application :

  git clone -b sample-react-map https://github.com/googlemaps/js-samples.git
  cd js-samples
  npm i
  npm start

Vous pouvez essayer d'autres exemples en basculant vers une branche commençant par sample-SAMPLE_NAME.

  git checkout sample-SAMPLE_NAME
  npm i
  npm start