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

johnvoncolln's avatar

Shipping rate retrieval using Livewire

When the user gets to the shipping rate page (after they have input the address), the page loads and there's a spinner that animates with the text "Fetching rates..." until the rates have loaded.

I've got this working, but having an issue with re-retrieving rates. On the shipping page, I have an option to change packaging - just a checkbox that emits an event called packagingOptionsChanged. This event then reloads the rates.

My problem is that I want the spinner to activate again, while rates are reloading, to give the user some visual feedback. But, I can't reset the $groupedRates property AND hit loadRates - it only works one or the other.

The idea is that when $groupedRates is set to false, the spinner (Fetching rates...) will show.

The question: how can I reset $groupedRates AND execute loadRates()?

As is, it will reload the rates, but no spinner with "Fetching Rates" shows...

Note: the rate fetching is an http call to an API, a whole different class outside of the component.

component:

<?php

namespace App\Http\Livewire\Checkout\Shipping;

use Livewire\Component;
use Cartalyst\Cart\Laravel\Facades\Cart;
use App\Support\Catalog\PrintPackageCalculator;
use App\Support\Cart\Shipping\ShippingRates as ShippingRatesCalculator;

class ShippingRates extends Component
{
    public $shipping_rate;

    protected $listeners = [
        'packagingOptionsChanged' => 'refreshRates',
    ];

    public function render()
    {

        return view('livewire.checkout.shipping.shipping-rates');
    }

    public function refreshRates()
    {
        $this->reset('groupedRates');
        $this->loadRates();
    }

    public function loadRates()
    {

        ... // get rates

        $this->groupedRates = $data['rates'];
    }
}

view:

<div wire:init="loadRates">
    <span><h4 class="ms-2">Shipping Method</h4></span>@error('shipping_rate')<p class="text-danger mb-3">Please choose a shipping method.</p>@enderror
    @if($groupedRates == false)
        <div class="row">
            <div class="col-md text-center mt-5">
                <div class="mt-5">
                    <p>Fetching rates...</p>
                    <div class="spinner-border text-danger" role="status">
                        <span class="visually-hidden">Loading...</span>
                    </div>
                </div>
            </div>
        </div>
    @else
    <div class="card @error('shipping_rate') bg-opacity-10 bg-danger @enderror p-2 text-dark bg-secondary border-0">
        <div class="card-body">
            <div class="table-responsive">
                @forelse($groupedRates as $carrier => $rates)
                <table class="table table-hover fs-sm mb-4 table-borderless">
                    <thead class="border-bottom">
                    <tr>
                        <th class="align-middle"></th>
                        <th class="align-middle">
                            <span class="fs-4">{{ $carrier }}</span>
                        </th>
                        <th class="align-middle"></th>
                        <th class="align-middle"></th>
                    </tr>
                    </thead>
                    <tbody>
                    @forelse($rates as $rate)
                    <tr>
                        <td>
                        <div class="form-check mb-4 is_invalid">
                            <input class="form-check-input" type="radio" id="courier" wire:model="shipping_rate" name="shipping_rate" value="{{ $rate['id'] }}">
                            <label class="form-check-label" for="courier"></label>
                        </div>
                        </td>
                        <td class="align-middle"><span class="text-dark fw-medium">{{ $rate['carrier'] }} {{ $rate['name'] }}</span><br><span class="text-muted">All addresses</span></td>
                        <td class="align-middle">{{ $rate['delivery_days'] }} @if($rate['delivery_days']) @if($rate['delivery_days'] > 1) days @else day @endif @else - @endif</td>
                        <td class="align-middle">${{ $rate['rate'] }}</td>
                    </tr>
                    @empty
                    There are no rates
                    @endforelse
                    </tbody>
                </table>
                @empty
                There are no rates
                @endforelse
                </div>
        </div>
    </div>
    @endif
</div>
0 likes
1 reply
LaryAI's avatar
Level 58

It seems like you want to show the spinner every time the rates are being fetched, including when the packagingOptionsChanged event is emitted. To achieve this, you should set $groupedRates to null or false before you start fetching the rates again. This will trigger the spinner to show up while the rates are being loaded.

Here's how you can modify your refreshRates and loadRates methods to ensure the spinner is shown during the rate fetching process:

public function refreshRates()
{
    $this->groupedRates = false; // Set to false to show the spinner
    $this->loadRates();
}

public function loadRates()
{
    // Simulate loading time (if necessary, remove if actual API call has its own delay)
    sleep(1); // Remove this line if your API call takes time and you don't need to simulate a delay

    // Perform the API call to fetch rates here
    // ...

    // Once rates are fetched, update the groupedRates property
    $this->groupedRates = $data['rates'];

    // If you need to force the component to re-render after setting groupedRates,
    // you can use $this->emitSelf('ratesLoaded') and listen for this event in your component.
}

In your Livewire component view, you are checking if $groupedRates is false to display the spinner. By setting $groupedRates to false before calling loadRates(), you ensure that the spinner is displayed while the rates are being fetched.

Make sure that your API call in loadRates is asynchronous or has a delay that justifies showing a spinner. If the API call is very fast, the spinner might not be visible to the user due to the quick state change.

If you are using Livewire's wire:loading directive, you can also apply it to show a loading state while an action is being performed. However, in this case, since you are dealing with a property change, the above solution should work well.

Please or to participate in this conversation.