react-google-maps: useMap() is always returning null for me.

Description

I have it wrapped under the APIProvider as well. This is where I have used it

// LocationSearchInput.js
import { useMap, useMapsLibrary } from "@vis.gl/react-google-maps";
import { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
import Loader from "../Loader/Loader";
import { Input } from "../ui/input";
import { Label } from "../ui/label";

interface Props {
  onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void;
  onChange: (_place: string | null) => void;
  value: string;
}

const mockPredictionResults = [
  {
    description: "Heathrow Airport (LHR), Hounslow, UK",
    place_id: "ChIJ6W3FzTRydkgRZ0H2Q1VT548",
  },
  {
    description: "Gatwick Airport (LGW), Crawley, UK",
    place_id: "ChIJK3mqp_lcX0gRrFtuiPUYr4Q",
  },
  {
    description: "Stansted Airport (STN), Stansted Mountfitchet, UK",
    place_id: "ChIJsyo6rpeb2EcRmUN8o6DfeAo",
  },
  {
    description: "Luton Airport (LTN), Luton, UK",
    place_id: "ChIJFbVSR_ePd0gRCHHv3E31DAo",
  },
  {
    description: "London City Airport (LCY), London, UK",
    place_id: "ChIJx74DkMMEdkgR22kJTzoy3QQ",
  },
];

const LocationSearchInput = ({ onChange, value, onPlaceSelect }: Props) => {
  const map = useMap();
  const places = useMapsLibrary("places");
  const [open, setOpen] = useState(true);
  const [loading, setLoading] = useState(false);

  const [inputValue, setInputValue] = useState<string>("");

  const [sessionToken, setSessionToken] =
    useState<google.maps.places.AutocompleteSessionToken>();

  const [autocompleteService, setAutocompleteService] =
    useState<google.maps.places.AutocompleteService | null>(null);

  const [placesService, setPlacesService] =
    useState<google.maps.places.PlacesService | null>(null);

  const [predictionResults, setPredictionResults] = useState<
    Array<google.maps.places.AutocompletePrediction>
  >([]);

  useEffect(() => {
    if (!places) return;

    setAutocompleteService(new places.AutocompleteService());
    setPlacesService(new places.PlacesService(map));
    setSessionToken(new places.AutocompleteSessionToken());

    return () => setAutocompleteService(null);
  }, [places]);

  const fetchPredictions = useCallback(
    async (inputValue: string) => {
      if (!autocompleteService || !inputValue) {
        setPredictionResults([]);
        return;
      }

      const request = { input: inputValue, sessionToken };
      const response = await autocompleteService.getPlacePredictions(request);
      setPredictionResults(response.predictions);
    },
    [autocompleteService, sessionToken]
  );

  const onInputChange = useCallback(
    (event: FormEvent<HTMLInputElement>) => {
      const value = (event.target as HTMLInputElement)?.value;
      setOpen(true);
      setInputValue(value);
      fetchPredictions(value);
    },
    [fetchPredictions]
  );

  const handleTestClick = (description: string) => {
    console.log("clicked");
    setInputValue(description);
    onChange(description);
    setOpen(false);
  };

  const handleSuggestionClick = useCallback(
    (placeId: string) => {
      if (!places) return;

      const detailRequestOptions = {
        placeId,
        fields: ["geometry", "name", "formatted_address"],
        sessionToken,
      };

      const detailsRequestCallback = (
        placeDetails: google.maps.places.PlaceResult | null
      ) => {
        onPlaceSelect(placeDetails);
        setPredictionResults([]);
        setInputValue(placeDetails?.formatted_address ?? "");
        setSessionToken(new places.AutocompleteSessionToken());
      };

      placesService?.getDetails(detailRequestOptions, detailsRequestCallback);
    },
    [onPlaceSelect, places, placesService, sessionToken]
  );

  const listView = useMemo(() => {
    if (open) {
      if (loading) {
        return <Loader />;
      } else if (inputValue.length > 0) {
        return (
          <ul className="border rounded-lg flex flex-col absolute top-12 z-10 bg-white ">
            {!!predictionResults.length ? (
              predictionResults.map(({ place_id, description }) => (
                <li
                  className="hover:bg-accent p-2 py-4 rounded-md text-sm cursor-pointer"
                  key={place_id}
                  onClick={() => handleSuggestionClick(place_id)}
                  // onClick={() => handleTestClick(description)}
                >
                  {description}
                </li>
              ))
            ) : (
              <Label className="text-sm py-8 text-center">
                No results found
              </Label>
            )}
          </ul>
        );
      }
    }
  }, [loading, handleSuggestionClick, predictionResults, inputValue, open]);

  return (
    <div className="flex flex-col gap-2 relative">
      <Input
        placeholder={`Search Location`}
        onInput={(event: FormEvent<HTMLInputElement>) => onInputChange(event)}
        // value={inputValue}
      />
      {listView}
    </div>
  );
};

export default LocationSearchInput;

And the APIProvider resides in MapsProvider:

import MapsProvider from "@/Providers/MapsProvider";
import Footer from "@/components/Footer";
import CustomHeader from "@/components/Header";
import { cn } from "@/lib/utils";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html suppressHydrationWarning lang="en" className="h-full overflow-hidden">
      <body
        className={cn(
          "flex flex-col h-screen bg-background font-sans antialiased text-bodyFont overflow-auto",
          inter.className
        )}
      >
        <MapsProvider>
          <CustomHeader />
          <div className="grow p-8">{children}</div>
          <Footer />
        </MapsProvider>
      </body>
    </html>
  );
}

Steps to Reproduce

Startup a next.js app wrap the app layout with api provider and use useMaps() underneath that

Environment

  • Library version: “@vis.gl/react-google-maps”: “^0.7.1”
  • Google maps version: weekly
  • Browser and Version: Microsoft Edge
  • OS: Windows

Logs

No response

About this issue

  • Original URL
  • State: closed
  • Created 4 months ago
  • Comments: 19

Most upvoted comments

ahh now that solves the issue thanks, i’ll try that

Primagen throwing hate on javascript is justified lol