0

I'm working on a Qwik project where I need to integrate Google Maps and its functionalities, such as autocomplete and map rendering, into my application. I want to create a reusable hook that loads the Google Maps API script once and provides components with these functionalities.

What would be the best approach to implement such a hook? Specifically, I'm looking for guidance on how to structure the hook, load the API script efficiently, and provide access to components with autocomplete and map functionalities.

Here's what I have in mind for the desired usage of the hook:

import GoogleMapsHook from './GoogleMapsHook';

function MyComponent() {
  const { mapComponent, autocompleteComponent } = GoogleMapsHook();

  return (
    <div>
      {mapComponent}
      {autocompleteComponent}
    </div>
  );
}

In the above code, I'm expecting the GoogleMapsHook to handle loading the Google Maps API script once, and then provide mapComponent and autocompleteComponent that I can render in my MyComponent.

What would be the ideal structure of the GoogleMapsHook? How can I ensure that the API script is loaded only once and subsequent components using the hook can access the loaded script and its functionalities seamlessly? Any suggestions or code examples demonstrating the implementation would be greatly appreciated.

Thanks in advance!

The first thing I think it would be the cleanest option was to create a hook that every time is called, loads the script (if its not loaded yet), initialize Map and Autocomplete instances and export a MapComponent and an AutocompleteComponent calling the hook, but it breaks the hook rules.

import {
  useStore,
  useVisibleTask$,
  useSignal,
  useTask$,
} from "@builder.io/qwik";
  
export function useGoogleMaps(options: any = {}) {
  const loaded = useSignal(false);
  const map = useSignal();
  const mapRef = useSignal<Element>();
  const autocomplete = useSignal()
  const autocompleteRef = useSignal<Element>();
  
  useVisibleTask$(async () => {
  if (!window.google || !window.google.maps) {
    loaded.value = false;
    const script = document.createElement("script");
    script.src = `https://maps.googleapis.com/maps/api/js?key=${import.meta.env.PUBLIC_GOOGLE_MAPS_API_KEY}&libraries=places&maps`;
    script.async = true;
    document.head.appendChild(script);
    loaded.value = true;
  } else {
    loaded.value = true;
  }
  });

  useTask$(async ({ track }) => { 
    track(loaded);
    if (loaded.value) {
      map.value = new window.google.maps.Map(
        mapRef.value,
        {
          center: { lat: -33.8688, lng: 151.2195 },
          zoom: 13,
        }
      );
      autocomplete.value = new window.google.maps.places.Autocomplete(
        autocompleteRef.value,
        options
      );
    }
  });
  
  return { map, autocomplete, mapRef, autocompleteRef };
}

const AutocompleteComponent = () => {
  const { autocompleteRef } = useGoogleMaps();
  return <input ref={autocompleteRef} type="text" 
            class="rounded-l-full w-full py-4 px-6 text-gray-700 leading-tight focus:outline-none"
            placeholder="Enter a location"
  />;
};

const MapComponent = () => {
  const { mapRef } = useGoogleMaps();
  return <div ref={mapRef} />;
}


export { AutocompleteComponent, MapComponent}
skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • Have you got a public repo to share please? – Giorgio Boa Jun 12 '23 at 19:20
  • Welcome to Stack Overflow! There's already an existing `google-maps-react-hooks` library from [ubilabs](https://github.com/ubilabs/google-maps-react-hooks). Have you checked this out? This would save you the time instead of creating your own. But if you still want to create your own, you can just take a look at their code then. – Yrll Jun 13 '23 at 02:27
  • btw google-maps-react-hooks is not working smoothly with Qwik. you can use react component in Qwik but you will loose the benefit of the new mental model of Qwik so it's better to have a specific integration for that – Giorgio Boa Jun 13 '23 at 10:18
  • Hello there, thanks for the responses. I don't have a public repository, but the code provided is what I have with importing the AutocompleteComponent and MapComponent components into the respective pages where they would be used. And as @GiorgioBoa says, I don't want to use a React library since it would lose the purpose of using Qwik, especially when it comes to the map – Ignacio Porte Stefoni Jun 15 '23 at 02:36

1 Answers1

0

Here is an example I created a global context to store Google Maps Object and a hook to attach the map to the dom.

import { type Signal, useContext, useVisibleTask$ } from '@builder.io/qwik';
import { AppContext } from '~/routes/layout';

export function useGoogleMaps(
    elementRefSignal: Signal<Element | undefined>
) {
    const appStore = useContext(AppContext);

    useVisibleTask$(async () => {
        new appStore.googleMaps.maps.Map(elementRefSignal.value!, {
            center: { lat: -34.397, lng: 150.644 },
            zoom: 8,
        });
    });
}
Giorgio Boa
  • 341
  • 4