ErrorPro / react-google-autocomplete

React components for google places API.
MIT License
477 stars 113 forks source link

Language prop is not reactive #169

Open MoritzKn opened 2 years ago

MoritzKn commented 2 years ago

The language can not be changed dynamically.

I realize this is because of a limitation in the Google Places Lib and the fact that the language is a parameter of the script URL itself:

https://github.com/ErrorPro/react-google-autocomplete/blob/214a7c3d4740d2bfcf85264717b0e0da5bd0b333/src/usePlacesWidget.js#L33

This means that this affects changing the language dynamically and also using two different languages on the same site.

Reproduction Example

function MyComponent() {
  const [lang, setLang] = useState("en");

  return (
    <>
      <button type="button" onClick={() => setLang("en")}>
        EN
      </button>
      <button type="button" onClick={() => setLang("de")}>
        DE
      </button>

      <Autocomplete
        apiKey={YOUR_GOOGLE_MAPS_API_KEY}
        language={lang}
        onPlaceSelected={(place) => console.log(place)}
      />
    </>
  );
}

Workaround

function unloadGooglePlacesLib() {
    // Only if the lib is loaded
  if (window.google && window.google) {
    // in theory deleting window.google.maps should be enough but react-google-autocomplete only checks for window.google
    // https://github.com/ErrorPro/react-google-autocomplete/blob/214a7c3d4740d2bfcf85264717b0e0da5bd0b333/src/utils.js#L20
    // this will break if you use other google libs
    delete window.google;
    document
      .querySelectorAll('script[src*="https://maps.googleapis.com"]')
      .forEach((scriptEl) => {
        scriptEl.remove();
      });

    // Needed to unload
    return true;
  }

  // Didn't need to do anything
  return false;
}

function MyComponent() {
  const [forceRerender, setForceRerender] = useState(false);
  const [lang, setLang] = useState("en");

  useEffect(() => {
    setForceRerender(false);
  }, [forceRerender]);

  // Props of the ReactGoogleAutocomplete end up as params of the Google Maps Places API script (the lib) that
  // is downloaded. When changing the props and even when recreating the component, these param changes don't
  // have any effect. To fix this we remove the scripts and recreate the component. This reloads the scripts.
  useEffect(() => {
    const neededToUnload = unloadGooglePlacesLib();
    setForceRerender(neededToUnload);
  }, [lang]);

  return (
    <>
      <button type="button" onClick={() => setLang("en")}>
        EN
      </button>
      <button type="button" onClick={() => setLang("de")}>
        DE
      </button>

      {!forceRerender && (
        <Autocomplete
          apiKey={YOUR_GOOGLE_MAPS_API_KEY}
          language={lang}
          onPlaceSelected={(place) => console.log(place)}
        />
      )}
    </>
  );
}

Proposed Solutions

  1. Dynamically reload the script like done in the workaround (this won't solve using different languages on the same site)
  2. Try to isolate the script somehow so multiple language versions can exist in parallel (not sure if this is even possible)
  3. Admit defeat but at least show a warning and document this limitation
ErrorPro commented 2 years ago

Yeah, good point, I'll try to implement the proposed solution. It indeed will require a reload of google maps api with a new language, which should block any iteration with the API until after the script is loaded.