Advanced - Current Location Search on Page Load | Yext Hitchhikers Platform

What You’ll Learn

In this section, you will:

  • Add logic to StoreLocator in order to run a current location search on page load

Overview

So far in this module, you have had to manually run a search on “New York City, New York, United States” in order to see changes to your locator. In this unit, you will add logic to your component in order to run a search based on the current location of the user when the page loads:


1. Add Current Location Search Logic to a useEffect hook

In StoreLocator.tsx, update your imports:

// src/components/StoreLocator.tsx

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

// component code...

Then, add some new logic to your component:

// src/components/StoreLocator.tsx

// imports...

type InitialSearchState = "not started" | "started" | "complete";

const StoreLocator = (): JSX.Element => {
  const searchActions = useSearchActions();

  // new code starts here...
  const [initialSearchState, setInitialSearchState] =
    useState<InitialSearchState>("not started");

  const searchLoading = useSearchState((state) => state.searchStatus.isLoading);

  useEffect(() => {
    getUserLocation()
      .then((location) => {
        searchActions.setStaticFilters([
          {
            selected: true,
            displayName: "Current Location",
            filter: {
              kind: "fieldValue",
              fieldId: "builtin.location",
              value: {
                lat: location.coords.latitude,
                lng: location.coords.longitude,
                radius: 40233.6, // equivalent to 25 miles
              },
              matcher: Matcher.Near,
            },
          },
        ]);
      })
      .catch(() => {
        searchActions.setStaticFilters([
          {
            selected: true,
            displayName: "New York City, New York, NY",
            filter: {
              kind: "fieldValue",
              fieldId: "builtin.location",
              value: {
                lat: 40.7128,
                lng: -74.006,
                radius: 40233.6, // equivalent to 25 miles
              },
              matcher: Matcher.Near,
            },
          },
        ]);
      })
      .then(() => {
        searchActions.executeVerticalQuery();
        setInitialSearchState("started");
      });
  }, []);

  useEffect(() => {
    if (!searchLoading && initialSearchState === "started") {
      setInitialSearchState("complete");
    }
  }, [searchLoading]);
  // ...and ends here
  

  const handleFilterSelect = (params: OnSelectParams) => {
    const locationFilter: SelectableStaticFilter = {
      displayName: params.newDisplayName,
      selected: true,
      filter: {
        kind: "fieldValue",
        fieldId: params.newFilter.fieldId,
        value: params.newFilter.value,
        matcher: Matcher.Equals,
      },
    };
    searchActions.setStaticFilters([locationFilter]);
    searchActions.executeVerticalQuery();
  };

  return (
    <>
      {/* new JSX starts here... */}
      <div className="relative flex h-[calc(100vh-210px)] border ">
        {initialSearchState !== "complete" && (
          <div className="absolute z-20 flex h-full w-full items-center justify-center bg-white opacity-70">
            <BiLoaderAlt className="animate-spin " size={64} />
          </div>
        )}
        {/* ...and ends here */}
        <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}
          />
        </div>
      </div>
    </>
  );
};

You added quite a bit of code so let’s review:

  • You’ve added the InitialSearchState type which is used as the type for initialSearchState.
  • The first useEffect hook will run the first time the StoreLocator component renders. getUserLocation will prompt the user to grant the site access to their location. If they grant location access, a location filter for the user’s location called “Current Location” will be set in the search state. If the user denies access, a filter will be set on the coordinates in New York City.
  • Once location access has been granted or denied, the then block will execute a vertical query and set initialSearchState to “started”.
  • The second useEffect hook will set initial search state to “complete” once the initial search has completed. You will use this state variable in your JSX.
  • While the component is waiting on the user to grant or deny usage of their location to the browser, you will use the initialSearchState flag to conditionally render a spinning loader icon. When the JSX is updated, a panel that contains a spinning icon over the results and map will appear.

If your site isn’t already running, start it back up with npm run dev and go the locator page. The browser should prompt you on whether or not you want to grant location access.

light bulb
Note
An alternative way of implementing current location search would be to run an initial search on set coordinates (e.g. 40.7128, -74.006) when the component renders without waiting for user permission. If the user grants permission, you could then run a second search after their location is provided.
unit Quiz
+20 points
Daily Quiz Streak Daily Quiz Streak: 0
Quiz Accuracy Streak Quiz Accuracy Streak: 0
    Error Success Question 1 of 1

    Which hook do we use to run a current location search on page load?

    You're out of this world! 🌎

    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