Search code examples
javascriptruby-on-railsgoogle-mapsstimulusjsimport-maps

Rails 7.1.2 + StimulusJS: Issue with Stimulus controller action after triggering a custom event on window


I am working on a Rails 7.1.2 application with Ruby 3.2.2 and facing an intermittent problem with Google Maps loading. The map sometimes loads in Firefox, but rarely in Brave. There seems to be an issue with a Stimulus controller action not consistently firing, although the event listener for the action seems to execute.

First, following some outdated guides, I started adding this script to my app/javascript/application.js

window.dispatchMapsEvent = function(...args) {
  const event = new CustomEvent("google-maps-callback", { detail: args });
  window.dispatchEvent(event);
}

window.addEventListener("google-maps-callback", function(event) {
  console.log("Google Maps API loaded");
})

and this to the head of my app/views/layouts/application.html.erb:

<%= javascript_include_tag "https://maps.googleapis.com/maps/api/js?key=#{Rails.application.credentials.google.api_key}&libraries=places&callback=dispatchMapsEvent",
                            defer: true,
                            async: true,
                            "data-turbolinks-eval": false
%>

Within this code, I got this error in my browser console:

Uncaught (in promise) InvalidValueError: dispatchMapsEvent is not a function

Although most of the guides I followed suggested this should work, I assumed the error was because the dispatchMapsEvent function was not yet loaded by the time the callback was executed, which is why I pulled the code out of the application.js and passed it to a script within the layout:

<script>
  window.dispatchMapsEvent = function(...args) {
    const event = new CustomEvent("google-maps-callback", { detail: args });
    window.dispatchEvent(event);
  }

  window.addEventListener("google-maps-callback", function(event) {
    console.log("Google Maps API loaded");
  })
</script>

<%= javascript_include_tag "https://maps.googleapis.com/maps/api/js?key=#{Rails.application.credentials.google.api_key}&libraries=places&callback=dispatchMapsEvent",
                            defer: true,
                            async: true,
                            "data-turbolinks-eval": false
%>

This seemed to fix the mentioned error, as the console.log ran correctly.

Secondly, I have configured a StimulusJS controller as follows:

views/folder/_some_partial.html.erb

<div  
    class="form-group"
    data-controller="localizators"
    data-action="google-maps-callback@window->localizators#initMap"
    data-localizators-current-location-value="<%= current_location %>"
  >

and then a app/javascript/controllers/localizators_controller.js:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    console.log('localizators controller connected');
  }

  initMap() {
    console.log('map init');
  }
}

The message 'localizators controller connected appears always', but the 'map init' never in Brave Browser, and sometimes in Firefox (I don't know how the browser is realted)

Just to give further information, I'm in my development environment, and is the same if I use bin/dev or rails s.

As you can see, there are two problems that I don't know if they are related.

The code I had to put as a JS script since with the recommended configuration it didn't seem to be loading and the Stimulus controller action was firing only sometimes, even though the event in @window seemed to be firing every time.

Thank you very much in advance for any help you can give me.


Solution

  • You should use loader package:
    https://www.npmjs.com/package/@googlemaps/js-api-loader

    $ bin/importmap pin @googlemaps/js-api-loader
    
    # make sure to have some height
    <%= tag.div class: "h-[400px]",
      data: {
        controller:                          :localizators,
        localizators_api_key_value:          Rails.application.credentials.google.api_key,
        localizators_current_location_value: {lat: -34.397, lng: 150.644}
      }
    %>
    
    // app/javascript/controllers/localizators_controller.js
    
    import { Controller } from "@hotwired/stimulus"
    import { Loader } from "@googlemaps/js-api-loader"
    
    // Connects to data-controller="localizators"
    export default class extends Controller {
      static values = {
        currentLocation: Object,
        apiKey: String,
      }
    
      connect() {
        this.loader = new Loader({
          apiKey: this.apiKeyValue,
          version: "weekly",
          // ...additionalOptions,
        });
    
        this.loader.load().then(async () => {
          const { Map } = await google.maps.importLibrary("maps");
    
          this.map = new Map(this.element, {
            center: this.currentLocationValue,
            zoom: 8,
          });
        });
      }
    }
    

    https://developers.google.com/maps/documentation/javascript/load-maps-js-api#js-api-loader