Add Custom Pin Components | Yext Hitchhikers Platform

What You’ll Learn

In this section, you will:

  • Create custom map pins for displaying locations on the map

Overview

In this unit, you will create a custom map pin that will match the style of your site and display the address of the location in a popup when you click on it:


In your new component, you’re going to be using some Mapbox GL types. Import them with the following command:

npm install --save @types/mapbox-gl

1. Create the Custom Pin Component

In src/components, create a new file called MapPin.tsx. Add the following code:

import * as React from "react";
import * as ReactDOM from "react-dom/server";
import { PinComponent } from "@yext/search-ui-react";
import { Popup, LngLatLike, Map } from "mapbox-gl";
import Location, { Coordinate } from "../types/locations";
import { useCallback, useEffect, useRef, useState } from "react";
import { GiTacos } from "react-icons/gi";
import { Result } from "@yext/search-headless-react";

const transformToMapboxCoord = (
  coordinate: Coordinate
): LngLatLike | undefined => {
  if (!coordinate.latitude || !coordinate.longitude) return;
  return {
    lng: coordinate.longitude,
    lat: coordinate.latitude,
  };
};

const getLocationHTML = (location: Location) => {
  const address = location.address;
  const html = (
    <div>
      <p className="font-bold">{location.neighborhood || "unknown location"}</p>
      <p>{location.address.line1}</p>
      <p>{`${address.city}, ${address.region}, ${address.postalCode}`}</p>
    </div>
  );
  return ReactDOM.renderToString(html);
};

const MapPin: PinComponent<Location> = ({
  index,
  mapbox,
  result,
}: {
  index: number;
  mapbox: Map;
  result: Result<Location>;
}) => {
  const location = result.rawData;
  const [active, setActive] = useState(false);
  const popupRef = useRef(
    new Popup({ offset: 15 }).on("close", () => setActive(false))
  );

  useEffect(() => {
    if (active && location.yextDisplayCoordinate) {
      const mapboxCoordinate = transformToMapboxCoord(
        location.yextDisplayCoordinate
      );
      if (mapboxCoordinate) {
        popupRef.current
          .setLngLat(mapboxCoordinate)
          .setHTML(getLocationHTML(location))
          .addTo(mapbox);
      }
    }
  }, [active, mapbox, location]);

  const handleClick = useCallback(() => {
    setActive(true);
  }, []);

  return (
    <button onClick={handleClick}>
      <GiTacos className="text-orange" size={30} />
    </button>
  );
};

export default MapPin;

Let’s review what you just added:

  • MapPin returns a button shaped like an orange taco. When the button is clicked, a popup will appear next to the pin with the neighborhood and address of the the location it represents.
  • The Popup class you imported from mapbox-gl has a method called setHTML that accepts a string parameter. getLocationHTML is used to generate a HTML string for the popup.

2. Pass MapPin as the PinComponent Prop

Now that your custom pin is ready, it can be used by the MapboxMap component to render a pin for each set of location coordinates.

In StoreLocator, update your imports to include the MapPin :

// src/components/StoreLocator.tsx

import * as React from "react";
import {
  MapboxMap,
  FilterSearch,
  OnSelectParams,
  VerticalResults,
} from "@yext/search-ui-react";
import {
  Matcher,
  SelectableStaticFilter,
  useSearchActions,
} from "@yext/search-headless-react";
// Mapbox CSS bundle
import "mapbox-gl/dist/mapbox-gl.css";
import LocationCard from "./LocationCard";
import MapPin from "./MapPin";                        // New

Then, pass MapPin as the PinComponent prop on MapboxMap:

// src/components/StoreLocator.tsx

// imports...

const StoreLocator = (): JSX.Element => {
  // ...other code from previous units

  return (
    <>
      <div className="flex h-[calc(100vh-210px)] border">
        <div className="w-1/3 flex flex-col">
          <FilterSearch
            onSelect={handleFilterSelect}
            placeholder="Find Locations Near You"
            searchFields={[
              {
                entityType: "location",
                fieldApiName: "builtin.location",
              },
            ]}
          />
          <VerticalResults
            customCssClasses={{ verticalResultsContainer: "overflow-y-auto" }}
            CardComponent={LocationCard}
          />
        </div>
        <div className="w-2/3">
          <MapboxMap
            mapboxAccessToken={YEXT_PUBLIC_MAPBOX_API_KEY || ""}
            PinComponent={MapPin}           // New 
          />
        </div>
      </div>
    </>
  );
};

If your site isn’t already running, start it back up with npm run dev and go the locator page. Search for “New York City, New York, NY” to see your new map pins.

unit Quiz
+20 points
Daily Quiz Streak Daily Quiz Streak: 0
Quiz Accuracy Streak Quiz Accuracy Streak: 0
    Error Success Question 1 of 2

    Where did the taco icon for the pin component come from?

    Error Success Question 2 of 2

    What content do you see when you click on a taco icon?

    Wahoo - you did it! 🙌

    You've already completed this quiz, so you can't earn more points.You completed this quiz in 1 attempt and earned 0 points! Feel free to review your answers and move on when you're ready.
1st attempt
0 incorrect
Feedback