Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

google.maps.Map is not a constructor when the page is refreshed #3419

Open
alamenai opened this issue Mar 24, 2025 · 0 comments
Open

google.maps.Map is not a constructor when the page is refreshed #3419

alamenai opened this issue Mar 24, 2025 · 0 comments

Comments

@alamenai
Copy link

Hi,

I used react-google-maps-api months ago in our next application and I noticed an issue that happens sometimes when I refresh the page.

I have this component:

"use client"

import { defaultLocation } from "@/features/viewer/components"
import { cn } from "@/lib/utils"
import { GoogleMap, StreetViewPanorama } from "@react-google-maps/api"
import { parseAsFloat, useQueryState } from "nuqs"

import { ViewProps } from "./google-view-card"

export const GoogleMapView = ({ viewMode, options }: ViewProps) => {
  const [lng] = useQueryState("lng", parseAsFloat.withDefault(defaultLocation[0]))
  const [lat] = useQueryState("lat", parseAsFloat.withDefault(defaultLocation[1]))

  const isStreetView = viewMode === "street"
  const isFullScreen = viewMode !== "both"

  return (
    <GoogleMap
      mapContainerClassName={cn("flex-1 w-full h-full rounded-t-none rounded-b-2xl", isFullScreen ? "min-h-[680px]" : "min-h-0")}
      center={{ lat, lng }}
      options={{
        ...options,
        tilt: isStreetView ? 0 : 45,
        mapTypeId: isStreetView ? "roadmap" : "satellite",
        zoom: isStreetView ? 10 : 20,
        fullscreenControl: isFullScreen,
        zoomControl: isFullScreen,
        cameraControl: true,
      }}
    >
      <StreetViewPanorama
        options={{
          addressControl: false,
          fullscreenControl: isFullScreen,
          zoomControl: isFullScreen,
          position: { lat, lng },
          visible: isStreetView,
          motionTracking: false,
          motionTrackingControl: false,
        }}
      />
    </GoogleMap>
  )
}

This component is used here:

import { cn } from "@/lib/utils"
import { Bird, CircleSlash, Columns2, Footprints, House, MapPinHouse, X, PanelLeftOpen, PanelRightOpen } from "lucide-react"
import { parseAsBoolean, parseAsInteger, useQueryState } from "nuqs"
import { useSelector } from "react-redux"

import { RootState } from "@/redux/store"

import { BuildingDataFeedback } from "@/components/building-data-feedback"
import { GoogleMapView } from "@/components/google-map-view"
import { GoogleViewCard, ViewMode } from "@/components/google-view-card"
import { Badge } from "@/components/ui/badge"
import { motion } from "framer-motion"
import { CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"

import { BuildingIndexToggle } from "./building-index-toggle"
import { BasicInfo } from "./infos/base"
import { ParcelInfo } from "./infos/parcel"
import { RoofInfo } from "./infos/roof-supestructure"
import { SurroundingContextInfo } from "./infos/surrounding-context"
import { ValuationInfo } from "./infos/valuation"
import { Button } from "@/components/ui/button"

export const BuildingInformationView = () => {
  const { buildingInformation } = useSelector((state: RootState) => state.geometry)

  const { city, houseNumber, street, zipCode } = useSelector((state: RootState) => state.address)
  const [buildingIndex] = useQueryState("buildingIndex", parseAsInteger.withDefault(-1))
  const [footPrintVisible] = useQueryState("footPrintVisible")
  const [buildingVisible] = useQueryState("buildingVisible")
  const [isVisible, setIsVisible] = useQueryState("isVisible", parseAsBoolean.withDefault(true))
  const [view, setView] = useQueryState<ViewMode | null>("view", {
    defaultValue: null,
    parse: (value) => value as ViewMode | null,
  })

  const toggleVisibility = () => {
    setIsVisible(!isVisible);
  };

  if (
    !buildingInformation ||
    buildingInformation.length === 0 ||
    footPrintVisible === null ||
    buildingVisible === null
  ) {
    return null
  }

  const building = buildingInformation[buildingIndex]

  return (
    <div className={cn('flex h-full gap-4 w-full', !isVisible && "w-8")}>
      <div
        className={cn(
          'flex h-full justify-between z-40 rounded-2xl bg-black/30 backdrop-blur-2xl border-none min-w-[480px] overflow-y-auto', 
          !isVisible && 'min-w-[72px] overflow-hidden'
        )}
        data-testid='building-information-view'
      >
        <BuildingIndexToggle buildingCount={buildingInformation.length} />

        <Button onClick={toggleVisibility} className=" bg-transparent absolute left-4 bottom-2 rounded-full text-sm z-50 w-10 h-10 p-2 hover:bg-black/20">
          {isVisible ? (
            <PanelRightOpen className="text-white" size={20}/>

          ) : (
            <PanelLeftOpen className="text-white" size={20}/>
          )}
        </Button>
        <motion.div 
          className='rounded-r-2xl rounded-l-none bg-black/10 backdrop-blur-2xl border-transparent overflow-y-auto building-information-view-scrollbar w-full' 
          animate={{
            opacity: isVisible ? 1 : 0,
          }}
          transition={{ duration: 0.2 }}
          >
            <CardHeader className='relative flex flex-col gap-2'>
            <div className='flex absolute items-center gap-2 top-3 right-3'>
              <Badge className='bg-rose-500'>Neu</Badge>
            </div>
            <div>
              <CardTitle className='text-white text-lg'>Gebäudeinformationen</CardTitle>
              <CardDescription className='text-white/70'>Wechseln Sie links zwischen den Gebäude-IDs</CardDescription>
            </div>
            <div className='border border-white/10 p-4 rounded-lg flex items-center gap-4 text-white'>
              <MapPinHouse />
              <h2>
                {street} {houseNumber}, {zipCode} {city}
              </h2>
            </div>
          </CardHeader>
          <CardContent className='flex flex-col mt-3'>
            <GoogleViewCard />
            <div className='w-full'>
              {building ? (
                <div className='flex flex-col gap-0 h-full'>
                  <BasicInfo buildingInfo={building.buildingInformation} />
                  <ValuationInfo valuationInfo={building.valuationInformation.buildingInformation} />
                  <SurroundingContextInfo contextInfo={{ surroundingContext: building.surroundingContext }} />
                  <ParcelInfo parcels={building.parcelInformation.parcels} />
                  <RoofInfo roofInfo={building.roofSuperstructureInformation} />
                </div>
              ) : (
                <EmptyState />
              )}
            </div>
            <BuildingDataFeedback />
          </CardContent>
        </motion.div>
      </div>

      {/* Views- always rendered but visibility controlled */}
      <div className='flex flex-col w-full h-full flex-1'>
        {view && (
          <div
            className='flex items-center justify-between p-2 border border-white/10 rounded-b-none rounded-t-2xl text-white z-50 bg-black/50 backdrop-blur-2xl'
            data-testid='google-views-toggle'
          >
            <ToggleGroup
              type='single'
              value={view as string}
              onValueChange={(value) => value && setView(value as ViewMode)}
              className='flex space-x-2'
            >
              <ToggleGroupItem
                value='street'
                className='data-[state=on]:bg-primary data-[state=on]:text-primary-foreground hover:bg-transparent hover:text-white/50 rounded-full min-w-24 gap-2'
              >
                <Footprints />
                Straße
              </ToggleGroupItem>
              <ToggleGroupItem
                value='immersive'
                className='data-[state=on]:bg-primary data-[state=on]:text-primary-foreground hover:bg-transparent hover:text-white/50 rounded-full min-w-24 gap-2 z-50'
                data-testid='google-immersive-view-item'
              >
                <Bird />
                Vogel
              </ToggleGroupItem>
              <ToggleGroupItem
                value='both'
                className='data-[state=on]:bg-primary data-[state=on]:text-primary-foreground hover:bg-transparent hover:text-white/50 rounded-full min-w-24 gap-2'
              >
                <Columns2 />
                Beide
              </ToggleGroupItem>
            </ToggleGroup>
            <Button onClick={() => setView(null)} className='w-10 h-10 p-0 bg-white hover:bg-white/70 text-white/50 rounded-full'>
              <X className="text-black" size={22} />
            </Button>
          </div>
        )}
        <div
          className={cn("relative flex flex-1 h-full w-full overflow-hidden z-50 p-2", !view && "hidden")}
          data-testid='google-map'
        >
          <div
            className='absolute top-0 left-0 h-full transition-all duration-300'
            style={{
              opacity: view === "street" || view === "both" ? 1 : 0,
              pointerEvents: view === "street" || view === "both" ? "auto" : "none",
              width: view === "both" ? "calc(50% - 8px)" : "100%", // Adjusting width for gap
              marginRight: view === "both" ? "8px" : "0", // Adding gap
            }}
          >
            <GoogleMapView viewMode='street' />
          </div>

          <div
            className='absolute top-0 right-0 h-full transition-all duration-300'
            style={{
              opacity: view === "immersive" || view === "both" ? 1 : 0,
              pointerEvents: view === "immersive" || view === "both" ? "auto" : "none",
              width: view === "both" ? "calc(50% - 8px)" : "100%",
              marginLeft: view === "both" ? "8px" : "0",
            }}
          >
            <GoogleMapView viewMode='immersive' />
          </div>
        </div>
      </div>
    </div>
  )
}

const EmptyState = () => {
  return (
    <div className='flex flex-col items-center text-center gap-6 w-full p-8 text-white'>
      <div className='relative'>
        <CircleSlash className='absolute -top-2 -left-2' />
        <House size={60} strokeWidth={1} />
      </div>

      <div className='flex flex-col gap-2'>
        <h2 className='text-lg'>Es ist kein Gebäude ausgewählt</h2>
        <p className='text-white/40 text-xs max-w-sm leading-5'>
          Wählen Sie Ihr Gebäude aus der Liste der Indexe aus, die Sie links sehen. Das ausgewählte Gebäude wird blau
          hervorgehoben.
        </p>
      </div>
    </div>
  )
}

I define the key in the layout ( I get the key through a server action ):

import { getEnv } from "@/actions/env"
import "@/globals.css"
import { cn } from "@/lib/utils"
import { deDE } from "@/localization/auth/de-DE"
import { ClerkProvider } from "@clerk/nextjs"
import { Inter as FontSans } from "next/font/google"
import Script from "next/script"
import "normalize.css"
import { NuqsAdapter } from "nuqs/adapters/next/app"

import { Header } from "@/components/app-header"
import { Providers } from "@/components/providers"
import { Toaster as Sonnertoaster } from "@/components/ui/sonner"
import { Toaster } from "@/components/ui/toaster"

import { metadata } from "./meta"

export const fontSans = FontSans({
  subsets: ["latin"],
  variable: "--font-sans",
})

export { metadata }

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const env = await getEnv()
  const googleMapsKey = env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!

  return (
    <ClerkProvider
      localization={deDE}
      afterSignOutUrl='/'
      afterMultiSessionSingleSignOutUrl='/'
      publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
    >
      <html lang='de' suppressHydrationWarning>
        <head>
          <link rel='apple-touch-icon' sizes='180x180' href='/favicons/apple-touch-icon.png' />
          <link rel='icon' type='image/png' sizes='32x32' href='/favicons/favicon-32x32.png' />
          <link rel='icon' type='image/png' sizes='16x16' href='/favicons/favicon-16x16.png' />
          <Script
            src={`https://maps.googleapis.com/maps/api/js?key=${googleMapsKey}&loading=async`}
            strategy='beforeInteractive'
          />
        </head>
        <NuqsAdapter>
          <body className={cn("min-h-screen bg-background font-sans antialiased overflow-x-hidden", fontSans.variable)}>
            <Providers>
              <div className='flex flex-col h-dvh'>
                <div className='fixed z-50 h-[64px] w-full flex justify-between'>
                  <Header />
                </div>
                <div className='flex-grow'>{children}</div>
              </div>
            </Providers>
            <Toaster />
            <Sonnertoaster richColors />
          </body>
        </NuqsAdapter>
      </html>
    </ClerkProvider>
  )
}

When I refresh the page sometimes, it says google.maps.Map is not a constructor:

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant