Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mkailbowdy's avatar

Implementing G Maps Place Javascript API into component

Hello all, Just a heads up, I'm still fairly new to the TALL stack and web dev in general!

I'm trying to add the Google Maps Place Javascript API into a Livewire component called MapComponent.php. When I insert the MapComponent into a form inside a full page livewire component (event-create.blade.php), the Google Maps text input is displayed and works, however the map doesn't render.

Any ideas on what I might be missing or doing wrong? Please let me know what other information I might need to provide y'all!

Below is my code:

maps.js

let map;
let marker;
let infoWindow;

async function initMap() {
    // Request needed libraries.
    //@ts-ignore
    const [{Map}, {AdvancedMarkerElement}] = await Promise.all([
        google.maps.importLibrary("marker"),
        google.maps.importLibrary("places"),
    ]);

    // Initialize the map.
    map = new google.maps.Map(document.getElementById("map"), {
        center: {lat: 35.6764, lng: 139.6500},
        zoom: 6,
        mapId: "4504f8b37365c3d0",
        mapTypeControl: false,
    });

    //@ts-ignore
    const placeAutocomplete = new google.maps.places.PlaceAutocompleteElement({
        componentRestrictions: {country: ['jp']},
        locationRestriction: map.getBounds(),
    });


    //@ts-ignore
    placeAutocomplete.id = "place-autocomplete-input";

    const card = document.getElementById("place-autocomplete-card");

    //@ts-ignore
    card.appendChild(placeAutocomplete);
    map.controls[google.maps.ControlPosition.TOP_LEFT].push(card);
    // Create the marker and infowindow
    marker = new google.maps.marker.AdvancedMarkerElement({
        map,
    });
    infoWindow = new google.maps.InfoWindow({});
    // Add the gmp-placeselect listener, and display the results on the map.
    //@ts-ignore
    placeAutocomplete.addEventListener("gmp-placeselect", async ({place}) => {
        await place.fetchFields({
            fields: ["displayName", "formattedAddress", "location"],
        });
        // If the place has a geometry, then present it on a map.
        if (place.viewport) {
            map.fitBounds(place.viewport);
        } else {
            map.setCenter(place.location);
            map.setZoom(17);
        }

        let content =
            '<div id="infowindow-content">' +
            '<span id="place-displayname" class="title">' +
            place.displayName +
            "</span><br />" +
            '<span id="place-address">' +
            place.formattedAddress +
            "</span>" +
            "</div>";

        updateInfoWindow(content, place.location);
        marker.position = place.location;
    });

    // Add listener to update bounds when they change
    map.addListener('bounds_changed', () => {
        placeAutocomplete.locationRestriction = map.getBounds();
    });
}

// Helper function to create an info window.
function updateInfoWindow(content, center) {
    infoWindow.setContent(content);
    infoWindow.setPosition(center);
    infoWindow.open({
        map,
        anchor: marker,
        shouldFocus: false,
    });
}

initMap();

livewire layout loads the css and js files

<head>
    <!-- Scripts -->
    @vite(['resources/css/app.css', 'resources/js/app.js'])
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <link rel="stylesheet" type="text/css" href="{{asset('css/maps.css')}}"/>
    <script type="module" src="{{asset('js/maps.js')}}"></script>
</head>

map-component.blade.php (MapComponent's view)

<div>
    <div class="place-autocomplete-card" id="place-autocomplete-card">
        <p>Search for a place here:</p>
    </div>
    <div id="map"></div>

    <!-- prettier-ignore -->
    <script>(g => {
            var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__",
                m = document, b = window;
            b = b[c] || (b[c] = {});
            var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams,
                u = () => h || (h = new Promise(async (f, n) => {
                    await (a = m.createElement("script"));
                    e.set("libraries", [...r] + "");
                    for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]);
                    e.set("callback", c + ".maps." + q);
                    a.src = `https://maps.${c}apis.com/maps/api/js?` + e;
                    d[q] = f;
                    a.onerror = () => h = n(Error(p + " could not load."));
                    a.nonce = m.querySelector("script[nonce]")?.nonce || "";
                    m.head.append(a)
                }));
            d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n))
        })
        ({key: "{{env('GOOGLE_MAPS_API_KEY')}}", v: "beta"});</script>
</div>

event-create.blade.php (a full page Livewire component)

    <form id="create" wire:submit.prevent="save" class="space-y-12">
// form cotents etc
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:py-6">
                    <label for="form.location" class="block text-sm font-medium text-gray-900 sm:pt-2">Location</label>
                    <div class="mt-1 sm:mt-0 sm:col-span-2">
                        <input type="text" name="form.location" id="form.location" autocomplete="form.location"
                               wire:model.blur="form.location"
                               class="block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                        @error('form.location')
                        <p class="mt-2 text-sm text-red-600">{{ $message }}</p>
                        @enderror
                    </div>
                    <livewire:map-component/>
                </div>
// form contents etc
    </form>
0 likes
1 reply
LaryAI's avatar
Level 58

To address the issue of the Google Maps not rendering in your Livewire component, there are a few steps you can take to ensure everything is set up correctly. Here are some potential solutions and improvements:

  1. Ensure the Google Maps API Key is Correct and Enabled: Make sure your Google Maps API key has the necessary permissions for the Maps JavaScript API and Places API.

  2. Check the HTML Structure: Ensure that the #map div has a defined height and width. Without these, the map might not render correctly.

  3. Load the Google Maps Script Correctly: Ensure the Google Maps script is loaded before your maps.js script. You can do this by including the script in your map-component.blade.php file.

  4. Initialize the Map After the Component is Rendered: Ensure that the map initialization code runs after the Livewire component is fully rendered. You can use Livewire's hook to achieve this.

Here is a revised version of your code with these considerations:

map-component.blade.php

<div>
    <div class="place-autocomplete-card" id="place-autocomplete-card">
        <p>Search for a place here:</p>
    </div>
    <div id="map" style="height: 500px; width: 100%;"></div>

    <!-- Load Google Maps Script -->
    <script>
        (g => {
            var h, a, k, p = "The Google Maps JavaScript API", c = "google", l = "importLibrary", q = "__ib__",
                m = document, b = window;
            b = b[c] || (b[c] = {});
            var d = b.maps || (b.maps = {}), r = new Set, e = new URLSearchParams,
                u = () => h || (h = new Promise(async (f, n) => {
                    await (a = m.createElement("script"));
                    e.set("libraries", [...r] + "");
                    for (k in g) e.set(k.replace(/[A-Z]/g, t => "_" + t[0].toLowerCase()), g[k]);
                    e.set("callback", c + ".maps." + q);
                    a.src = `https://maps.${c}apis.com/maps/api/js?` + e;
                    d[q] = f;
                    a.onerror = () => h = n(Error(p + " could not load."));
                    a.nonce = m.querySelector("script[nonce]")?.nonce || "";
                    m.head.append(a)
                }));
            d[l] ? console.warn(p + " only loads once. Ignoring:", g) : d[l] = (f, ...n) => r.add(f) && u().then(() => d[l](f, ...n))
        })
        ({key: "{{ env('GOOGLE_MAPS_API_KEY') }}", v: "beta"});
    </script>

    <!-- Initialize Map after Livewire Component is Rendered -->
    <script>
        document.addEventListener('livewire:load', function () {
            initMap();
        });
    </script>
</div>

maps.js

let map;
let marker;
let infoWindow;

async function initMap() {
    // Request needed libraries.
    const [{Map}, {AdvancedMarkerElement}] = await Promise.all([
        google.maps.importLibrary("marker"),
        google.maps.importLibrary("places"),
    ]);

    // Initialize the map.
    map = new google.maps.Map(document.getElementById("map"), {
        center: {lat: 35.6764, lng: 139.6500},
        zoom: 6,
        mapId: "4504f8b37365c3d0",
        mapTypeControl: false,
    });

    const placeAutocomplete = new google.maps.places.PlaceAutocompleteElement({
        componentRestrictions: {country: ['jp']},
        locationRestriction: map.getBounds(),
    });

    placeAutocomplete.id = "place-autocomplete-input";

    const card = document.getElementById("place-autocomplete-card");
    card.appendChild(placeAutocomplete);
    map.controls[google.maps.ControlPosition.TOP_LEFT].push(card);

    // Create the marker and infowindow
    marker = new google.maps.marker.AdvancedMarkerElement({
        map,
    });
    infoWindow = new google.maps.InfoWindow({});

    // Add the gmp-placeselect listener, and display the results on the map.
    placeAutocomplete.addEventListener("gmp-placeselect", async ({place}) => {
        await place.fetchFields({
            fields: ["displayName", "formattedAddress", "location"],
        });

        // If the place has a geometry, then present it on a map.
        if (place.viewport) {
            map.fitBounds(place.viewport);
        } else {
            map.setCenter(place.location);
            map.setZoom(17);
        }

        let content =
            '<div id="infowindow-content">' +
            '<span id="place-displayname" class="title">' +
            place.displayName +
            "</span><br />" +
            '<span id="place-address">' +
            place.formattedAddress +
            "</span>" +
            "</div>";

        updateInfoWindow(content, place.location);
        marker.position = place.location;
    });

    // Add listener to update bounds when they change
    map.addListener('bounds_changed', () => {
        placeAutocomplete.locationRestriction = map.getBounds();
    });
}

// Helper function to create an info window.
function updateInfoWindow(content, center) {
    infoWindow.setContent(content);
    infoWindow.setPosition(center);
    infoWindow.open({
        map,
        anchor: marker,
        shouldFocus: false,
    });
}

event-create.blade.php

<form id="create" wire:submit.prevent="save" class="space-y-12">
    <!-- form contents etc -->
    <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:py-6">
        <label for="form.location" class="block text-sm font-medium text-gray-900 sm:pt-2">Location</label>
        <div class="mt-1 sm:mt-0 sm:col-span-2">
            <input type="text" name="form.location" id="form.location" autocomplete="form.location"
                   wire:model.blur="form.location"
                   class="block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
            @error('form.location')
            <p class="mt-2 text-sm text-red-600">{{ $message }}</p>
            @enderror
        </div>
        <livewire:map-component/>
    </div>
    <!-- form contents etc -->
</form>

app.js

Ensure that maps.js is imported in your app.js if you are using a module bundler like Vite.

import './maps';

By ensuring the map container has a defined height and width, loading the Google Maps script correctly, and initializing the map after the Livewire component is rendered, you should be able to get the map to display correctly.

Please or to participate in this conversation.