lemonotype's avatar

Livewire 4-level dependent dropdown not working reliably.

Hello, I'm very new to it all and am trying to set up a four level dependent dropdown. I followed a tutorial for a three level dropdown and also checked the code in every other tutorial I found on these dropdowns (few and far between) but they all looked roughly the same.

I have two main issues:

  • When using orderBy('name') on get() query, it works fine the first time around but when I select an item it changes to what would've been in that spot without orderBy. I don't understand why this is happening, why is it loading it twice and isn't it using the same logic both times anyway? I should note it only changes graphically, the second dropdown is loaded correctly for the (first) selection. I thought it might also be causing issue two, but if I remove orderBy, issue 2 still happens.
  • It seems to consistently "crash" when I select a different sublevel the first time closing everything, but works okay if you keep changing values (most of the time).

My livewire component:

<div class="col-span-full">

    {{-- Make --}}
    <div class="sm:col-span-full">
        <div class="relative mt-2">
            <select wire:model.live="selectedMake" id="make" name="make" autocomplete="off" class="block px-3 pb-3.5 pt-5 w-full text-sm text-gray-900 bg-transparent rounded-[5px] border-1 border-gray-300 appearance-none dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-inset focus:border-indigo-600 peer">
                <option>Select make</option>
                @foreach ($makes as $id => $make)
                    <option value="{{ $make->id }}">{{ $make->name }}</option>
                @endforeach
            </select>
            <label for="make" class="absolute text-xs text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-70 top-5 z-10 origin-[0] px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-7 peer-focus:top-5 peer-focus:scale-70 peer-focus:-translate-y-4 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto start-1">Make</label>
        </div>
    </div>

    {{-- Car --}}
    @if(!is_null($selectedMake))

    <div wire:transition class="sm:col-span-full">
        <div class="relative mt-2">
            <select wire:model.live="selectedCar" id="car" name="car" autocomplete="off" class="block px-3 pb-3.5 pt-5 w-full text-sm text-gray-900 bg-transparent rounded-[5px] border-1 border-gray-300 appearance-none dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-inset focus:border-indigo-600 peer">
                <option>Select car</option>
                @foreach ($cars as $car)
                    <option value="{{ $car->id }}">{{ $car->name }}</option>
                @endforeach
            </select>
            <label for="car" class="absolute text-xs text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-70 top-5 z-10 origin-[0] px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-7 peer-focus:top-5 peer-focus:scale-70 peer-focus:-translate-y-4 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto start-1">Car</label>
        </div>
    </div>

    @endif

    {{-- Generation --}}
    @if(!is_null($selectedCar))

    <div wire:transition class="sm:col-span-full">
        <div class="relative mt-2">
            <select wire:model.live="selectedGeneration" id="generation" name="generation" autocomplete="off" class="block px-3 pb-3.5 pt-5 w-full text-sm text-gray-900 bg-transparent rounded-[5px] border-1 border-gray-300 appearance-none dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-inset focus:border-indigo-600 peer">
                <option>Select generation</option>
                @foreach ($generations as $generation)
                    <option value="{{ $generation->id }}">{{ $generation->name }}</option>
                @endforeach
            </select>
            <label for="generation" class="absolute text-xs text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-70 top-5 z-10 origin-[0] px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-7 peer-focus:top-5 peer-focus:scale-70 peer-focus:-translate-y-4 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto start-1">Generation</label>
        </div>
    </div>
   
    @endif

    {{-- Powertrain --}}
    @if(!is_null($selectedGeneration))

    <div wire:transition class="sm:col-span-full">
        <div class="relative mt-2">
            <select wire:model.live="selectedPowertrain" id="powertrain" name="powertrain" autocomplete="off" class="block px-3 pb-3.5 pt-5 w-full text-sm text-gray-900 bg-transparent rounded-[5px] border-1 border-gray-300 appearance-none dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-inset focus:border-indigo-600 peer">
                <option>Select Powertrain</option>
                @foreach ($powertrains as $powertrain)
                    <option value="{{ $powertrain->id }}">{{ $powertrain->name }}</option>
                @endforeach
            </select>
            <label for="powertrain" class="absolute text-xs text-gray-500 dark:text-gray-400 duration-300 transform -translate-y-4 scale-70 top-5 z-10 origin-[0] px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-7 peer-focus:top-5 peer-focus:scale-70 peer-focus:-translate-y-4 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto start-1">Powertrain</label>
        </div>
    </div>

    @endif

</div>

CarSelect.php

<?php

namespace App\Livewire;

use App\Models\Make;
use App\Models\Car;
use App\Models\Generation;
use App\Models\Powertrain;
use Livewire\Component;

class CarSelect extends Component
{

    // Defining all the variables.
    public $makes;
    public $cars;
    public $car;
    public $generations;
    public $powertrains;
    public $years;
    public $transmissions;
    public $fuels;

    // Defining the default select states.
    public $selectedMake = null;
    public $selectedCar = null;
    public $selectedGeneration = null;
    public $selectedPowertrain = null;

    // When the component is loaded, we get all the car makes.
    public function mount()
    {
        $this->makes = Make::orderBy('name')->get();
    }

    // Functions that update the selected.
    public function updatedSelectedMake($make)
    {
        if (!is_null($make)) {
            $this->cars = Car::where('make_id', $make)->orderBy('name')->get();
            $this->car = $this->cars->first()->id ?? null;
        }

        $this->selectedCar = null;
    }

    public function updatedSelectedCar($car)
    {
        if (!is_null($car)) {
            $this->generations = Generation::where('car_id', $car)->orderBy('name')->get();
            $this->appendYearsToGenerations();
        }

        $this->selectedGeneration = null;
    }

    public function updatedSelectedGeneration($generation)
    {
        if (!is_null($generation)) {
            $this->appendYearsToGenerations();
            $this->powertrains = Powertrain::where('generation_id', $generation)->orderBy('name')->get();
        }

        $this->selectedPowertrain = null;
    }

    public function updatedSelectedPowertrain($powertrain)
    {
        if (!is_null($powertrain)) {
            $this->appendYearsToGenerations();
        }
    }

    // Function to add the Generation min-max years, gotten from the Powertrains.
    public function appendYearsToGenerations()
    {
        foreach ($this->generations as $generation) {
            $minYear = Powertrain::where('generation_id', $generation['id'])->min('year_begin');
            $maxYear = Powertrain::where('generation_id', $generation['id'])->max('year_end');
   
            $generationNameWithYears = $generation->name . ' (' . $minYear . ' - ' . $maxYear . ')';
            $generation->name = $generationNameWithYears;
        }
    }

    // Gets the view file for this component.
    public function render()
    {
        return view('livewire.car-select');
    }
}

I know it's pretty repetitive and could probably use a refactor but I'd appreciate any help in at least getting the dang thing to work reliably fist, thank you in advance!

PS: I made a fancy gif to illustrate but I still can't post links even though I've been waiting three days to post this :(

0 likes
3 replies
Snapey's avatar
Snapey
Best Answer
Level 122

where are your wire:key ?

lemonotype's avatar

@Snapey Thank you, I had seen those, implemented them without fully understanding (and unsure they were related as none of the guides I checked had them) and removed them. Now I found live wire documentation that specifically mentions my exact issues and the cure is keys, I'll test tomorrow.

lemonotype's avatar

@Snapey So wire:key's on the s fixed losing selection, deleting wire:transition's got rid of the disappearing act and everything is working properly now, thank you. My only issue left is that after selecting something and the dropdown re-renders, it's losing the "->orderBy('name')" and goes back to the same order as in the DB, any ideas about that?

Please or to participate in this conversation.