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

tvbz's avatar
Level 4

Livewire + Google Maps api: Why is my collection empty?

I am building a livewire component that renders a Google maps with markers. I have many filters to search for locations depending on preferences. All of these work fine.. But when I wanted to add a locations count, I had an error: count(): Argument #1 ($var) must be of type Countable|array, null given.

At first the count appears fine, but a second later it jumps to this error. If I remove the {{$locations->count()}} and just echo out {{$locations}}, a list of locations appears and then a second later disappears. So the collection is reset to null.

Who can help me understand why? For readability I have removed most functions from controller en view. Only the bug remains. I was able to pinpoint the issue is only there when the updateMapPosition($latlng, $zoom) happens.

My controller:

<?php

namespace App\Http\Livewire;

use App\Models\Country;
use Illuminate\Support\Facades\DB;
use Livewire\Component;

class Test extends Component
{
    protected $listeners = ['updateMapPosition'];
    private $locations;

    public $zoom;
    public $latlng;

    public $country;

    public $queryString = [
        'zoom' => [
            'except' => '3',
            'as' => 'z',
        ],
        'latlng' => [
            'except' => '15,0',
            'as' => 'p',
        ],
        'country' => [
            'except' => '',
            'as' => 'c',
        ],
    ];

    public function mount()
    {

        $this->zoom = request('z') ?? '3';
        $this->latlng = request('p') ?? '15,0';
        $this->country = request('c') ?? '';

        $this->locations = $this->getLocationsFromDB();
    }

    public function filterLocations()
    {
        $this->locations = $this->getLocationsFromDB();
    }

    public function updateMapPosition($latlng, $zoom)
    {
        $this->latlng = $latlng;
        $this->zoom = $zoom;
    }

    // get the filtered collection
    private function getLocationsFromDB()
    {
        $db = DB::table('locations AS l')
            ->select('l.id', 'l.name', 'l.latitude', 'l.longitude')
            ->where('l.status', 1);
        if ($this->country) {
            $db
                ->join('countries AS c', 's.country_id', '=', 'c.id')
                ->where('c.id', $this->country);
        }

        $locations = $db->distinct()->get();

        return $locations;
    }

    public function render()
    {
        return view('livewire.test', [
            'locations' => $this->locations,
            'countries' => Country::has('locations')->get(),
        ]);
    }
}

Template:

<div id="wrapper">
    <div id="filter">
        {{$locations->count()}}

        <div class="filter">
            <label class="h3" for="country">Search by country</label>
            <select wire:model="country" wire:change="filterLocations" name="country" id="country">
                <option value="" selected>-- all countries ---</option>
                @foreach ($countries as $country)
                    <option value="{{ $country->id }}">{{ $country->name }}</option>
                @endforeach
            </select>
        </div>

    </div>
    <div id="map-container">
        {{ $locations }}
        <div wire:ignore id="map"></div>
    </div>
</div>

@push('scripts')
    <script>
        let map;
        let markers = [];

        function initMap() {
            let [latitude, longitude] = @js($latlng).split(',');
            const latLng = {
                lat: parseFloat(latitude),
                lng: parseFloat(longitude)
            };

            map = new google.maps.Map(document.getElementById("map"), {
                center: latLng,
                zoom: parseInt({{ $zoom }}),
                scrollwheel: true,
            });

            // listen to map changes & update map position
            map.addListener("idle", function() {
                let zoom = map.getZoom();
                let latLng = map.getCenter().lat() + "," + map.getCenter().lng();
                changeMapPosition(zoom, latLng);
            });
        };

        // update map position
        function changeMapPosition(zoom, latLng) {
            Livewire.emit("updateMapPosition", latLng, zoom);
        }

    </script>
    <script
        src="https://maps.googleapis.com/maps/api/js?key={{ config('app.google_api_key') }}&language=en&callback=initMap">
    </script>
@endpush

Thanks for your insights!

0 likes
5 replies
tvbz's avatar
Level 4

@idew Thanks. I was passing the private property in render function. But indeed, removing it from render function and simply using public property fixed it. Thanks!

1 like
LongSu's avatar

@idew Nice to meet you.

Hi everyone! How are you?

I am trying to load google map api link in livewire modal component, but I assure that it's just asset link. So, I added it in @assets directive , after that , I created callback function which will be calling from google map api. So something like this. This is a modal component blade file.

<div>
    <div>
        <x-input-label for="geoAddress" :value="__('Search Address for Map')" />
        <input wire:model.blur="geoAddress" type="text" id="geoAddress" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full px-4 py-2 mt-2" required />
    </div>
    <div wire:ignore>
        <div id="address-map-container" class="mt-2 w-full h-[100px]">
            <div style="width: 100%; height: 100%" id="map"></div>
        </div>
    </div>
</div>

@assets
<script src="https://maps.googleapis.com/maps/api/js?key={My google map api key}&callback=initAutocomplete&libraries=places&v=weekly" defer></script>
@endassets

@script
<script>
function initAutocomplete() {
	// my code here.
}
</script>
@endscript

But when I open a modal, error is showing with "Uncaught (in promise) InvalidValueError: initAutocomplete is not a function". I know this error why. It's because initAutomcomplete() function will be defined after fully loaded links in @assets. Livewire 3 document mentions that @script directive will be loaded after fully loaded links in @assets.

So, I tried to put google map api link like this. But I know this will not work.

@script
<script>
function initAutocomplete() {
	// my code here.
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key={My google map api key}&callback=initAutocomplete&libraries=places&v=weekly" defer></script>
@endscript

Then, I assume , there is no way to load google map api link. Please help me how to load api link or let me know if there is any google map api package that I can use with livewire 3. thank you so much!

webrobert's avatar

@tvbz you have to go both ways. So you moved the map. And told Livewire. But you have no function in your JavaScript to load new pins. I gave you the answer in your other question.

Edit. You have the initial load state. When the dom loads. Then if a Livewire component changes the map should change. But also if the map is engaged it should notify Livewire and Livewire needs to be able to respond via JavaScript for when the dom is already loaded.

tvbz's avatar
Level 4

@webrobert I actually have all the functions in JS but I just cleaned it out because it's a lot of JS in this component. In full component I had same issue. When commenting out the changeMapPosition, the issue was not there, that's why I cleaned out all other code. :)

I tried changing $locations to public property instead of passing it in render function (like @idew suggested) and that fixed it.

1 like

Please or to participate in this conversation.