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
ahh now that solves the issue thanks, i’ll try that
Primagen throwing hate on javascript is justified lol