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

Next.js Integration #1036

Open
paularah opened this issue Jun 9, 2022 · 13 comments
Open

Next.js Integration #1036

paularah opened this issue Jun 9, 2022 · 13 comments
Labels
help wanted Extra attention is needed

Comments

@paularah
Copy link

paularah commented Jun 9, 2022

I would appreciate a definitive guide on using this with next.js. The Next.js script tag with a beforeInteractive strategy gives the error use-places-autocomplete: Google Maps Places API library must be loaded. The native script tag only works with asynchronous loading since external synchronous scripts are forbidden in Next.js. asynchronously loading the script occasionally freezes the input field.

@clearly-outsane
Copy link

I have this problem too

@wellyshen wellyshen added the help wanted Extra attention is needed label Jul 11, 2022
@DopamineDriven
Copy link

Per the recent Nextjs shift in best practices for loading beforeInteractive scripts, you can instantiate the library as pictured and it works perfectly. I’m using next@latest, edge middleware, etc etc; works with next@12.x.x
AA66211F-B6D6-49DC-85FB-90708DD14C6A

if anyone has tips for using the returned latlng values with Algolia InstantSearch Hooks it’d be much appreciated. I currently have it all working in isolation; but when using both Google places and algolia in the same form the fetching strategies clash I think (fiddling with Promise.all([]) but might say fuck it and use global context, keep the worlds separate).

That said, it would be dope to see an algolia-instantsearch-hooks use places autocomplete integration
4ED8D13C-9C3E-4A1F-88C6-5611F3A8B35A

@scottschindler
Copy link

scottschindler commented Jul 13, 2022

@DopamineDriven Thanks.

Is that code in "pages/_document.js"? I have similar code in document and it's not working for me.

import { Html, Head, Main, NextScript } from "next/document";
import Script from "next/script";

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
        <Script
          id="googlemaps"
          type="text/javascript"
          strategy="beforeInteractive"
          src="https://maps.googleapis.com/maps/api/js?key=KEY-4Q&libraries=places"
        />
      </body>
    </Html>
  );
}

@dgaitsgo
Copy link

dgaitsgo commented Jul 22, 2022

Without making the script available everywhere, putting it in _document, only this helped resolve the issue: #883 (comment)

Edit: the above only partially worked, since it would load once and no longer work on refresh.
Workaround: vercel/next.js#17919 (comment)

@avaneeshtripathi
Copy link

avaneeshtripathi commented Oct 7, 2022

Reason:
This issue occurs when usePlacesAutocomplete is initialized before the Google Maps script is loaded.

Solution:
This can be accomplished with any of the strategies.

  1. beforeInteractive should anyways work as the google map script is loaded initially. But if it doesn't, same solution can be applied for that as well.
  2. if we have issues with afterInteractive or lazyOnLoad. we can do the following:

A. We set a flag somewhere in redux/zustand/context when the google maps script is loaded. The placement of script tag will depend on the use cases like:

  • If we are using external stores like zustand we can placed the script in _document.tsx
  • In case of context, the script needs to be places within the boundaries of context to be able to set the flag there.
<Script
      id="googlemaps"
      type="text/javascript"
      strategy="lazyOnLoad"
      src="https://maps.googleapis.com/maps/api/js?key=KEY-4Q&libraries=places"
      onLoad={() => useMapStore.setState({ isMapScriptLoaded: true })}
/>

B. Wherever we are using usePlacesAutoComplete, we delay initialization of the hook based on our flag:

const isMapScriptLoaded = useMapStore((state) => state.isMapScriptLoaded);

const { init } = usePlacesAutocomplete({ initOnMount: false });

useEffect(() => {
    if (isMapScriptLoaded) {
        init();
    }
}, [isMapScriptLoaded]);

The above example uses a zustand store, same can be implemented for redux or context as well.

@waldothedeveloper
Copy link

waldothedeveloper commented Nov 10, 2022

Hey there: @paularah
What I have on my NextJS app v. 12.2.5 and seems to be working is putting the <Script /> from NextJs like this on the _document.js page.

image

However, this doesn't work for me when I put it on a single page inside the pages folder. So I guess the downside to it is that it will load mostly on every page, perhaps hurting performance, but I'm not 100% sure, so I don't want to claim things like that.

In short, putting it on _document.js should work!

@nyfever007
Copy link

I had same problem.
After some digging, I was able to work around using "use-google-maps-script" package hook.

Using this package, I was able to wait until script is loaded and then call the component to usePlacesAutocomplete.

@paularah
Copy link
Author

@nyfever007 any chance you can paste a snippet? I tried reproducing your approach with no success.

@nyfever007
Copy link

nyfever007 commented Dec 12, 2022

@paularah By using use-google-maps-script package you can wait until script is loaded then call your address component.

import { Libraries, useGoogleMapsScript } from 'use-google-maps-script';
const libraries: Libraries = ['places'];
const App = () => {
  const { isLoaded, loadError } = useGoogleMapsScript({
    googleMapsApiKey: process.env.NEXT_PUBLIC_GOOLGE_MAPS_API_KEY!,
    libraries,
  });
return(
 <div>
  {isLoaded && (
          <AutoPlacesAddress setSelectedAddress={setSelectedAddress}    defaultValue={null}  />
        )}
</div>
  )
}
export default App
import { useRef } from 'react';
import usePlacesAutocomplete, {  getDetails,  getGeocode,  getLatLng,} from 'use-places-autocomplete';
import { useOnClickOutside } from 'usehooks-ts';

type Props = {  setSelectedAddress: React.Dispatch<React.SetStateAction<AddressFormValues>>;
  defaultValue: string | null;};
const AutoPlacesAddress = ({ setSelectedAddress, defaultValue }: Props) => {
  const {
    ready,
    value,
    suggestions: { status, data },
    setValue,
    clearSuggestions,
  } = usePlacesAutocomplete({
    requestOptions: {
      /* Define search scope here */
    },
    debounce: 500,
  });
  const handleInput = (e: React.ChangeEvent<HTMLInputElement>): void => {
    setValue(e.target.value);
  };
  const ref = useRef(null);
  const handleClickOutside = () => {
 // use useOnClickOutside hook to close the suggestions when user clicks outside of the component
    clearSuggestions();
  };
  useOnClickOutside(ref, handleClickOutside);
  const handleSelect =
    (suggestion: { description: string; place_id: string }) => async () => {
      // When user selects a place, we can replace the keyword without request data from API
      // by setting the second parameter to "false"
      setValue(suggestion.description, false);
      const placeId = {
        placeId: suggestion.place_id,
      };
      clearSuggestions();
      try {
       const latlng = await getGeocode({ address: suggestion.description });
        const { lat, lng } = getLatLng(latlng[0]);
        const detail: any = await getDetails(placeId);
           setSelectedAddress({
          address: suggestion.description,
          lat: lat,
          lng: lng,
        });
      } catch (error) {
        console.log(error);
      }
    };
  const renderSuggestions = () =>
    data.map((suggestion) => {
      const {
        place_id,
        structured_formatting: { main_text, secondary_text },
      } = suggestion;
      return (
        <li
          key={place_id}
          onClick={handleSelect(suggestion)}
          className='w-full cursor-pointer border-b py-1 px-4  last-of-type:border-0 hover:bg-indigo-500 hover:text-white'
        >
          <div>
            {main_text} <small>{secondary_text}</small>
          </div>
        </li>
      );
    });


  return (
    <>
      <div ref={ref} className='relative'>
        <input
          value={value}
          onChange={handleInput}
          disabled={!ready}
          placeholder={defaultValue!}
          className='form-input w-full rounded-md border-neutral-300 outline-none focus:border-dark-900 focus:outline-0 focus:ring-0'
        />
        {/* We can use the "status" to decide whether we should display the dropdown or not */}
        {status === 'OK' && (
          <ul className='absolute top-10 w-full rounded-md border bg-neutral-50'>
            {renderSuggestions()}
          </ul>
        )}
      </div>
    </>
  );
};
export default AutoPlacesAddress;

@pckilgore
Copy link

If you want to dynamically load in the Google script when component mounts, this worked for me:

// hook
  const { init /*, ...rest */ } = usePlacesAutocomplete({
    initOnMount: false,
  });


// component
<Script
  src="https://maps.googleapis.com/maps/api/js?key=${GOOGLE_PLACES_KEY}&libraries=places"
  onReady={init}
/>

@agboolaidris
Copy link

Adding defer solve this issue for me

<Script defer id="googlemaps" src="https://maps.googleapis.com/maps/api/js?key=''&libraries=places" strategy="beforeInteractive" type="text/javascript" />

@KarenBoyakhchyan
Copy link

Per the recent Nextjs shift in best practices for loading beforeInteractive scripts, you can instantiate the library as pictured and it works perfectly. I’m using next@latest, edge middleware, etc etc; works with next@12.x.x AA66211F-B6D6-49DC-85FB-90708DD14C6A

if anyone has tips for using the returned latlng values with Algolia InstantSearch Hooks it’d be much appreciated. I currently have it all working in isolation; but when using both Google places and algolia in the same form the fetching strategies clash I think (fiddling with Promise.all([]) but might say fuck it and use global context, keep the worlds separate).

That said, it would be dope to see an algolia-instantsearch-hooks use places autocomplete integration 4ED8D13C-9C3E-4A1F-88C6-5611F3A8B35A

Did you have working like this?

Screen Shot 2023-05-11 at 20 09 10
Thanks

@picozzimichele
Copy link

@avaneeshtripathi thanks a lot! Your solution with init() worked well. Had issue waiting for loads on refresh and this solves it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests