Search code examples
phplaravellaravel-livewire

Laravel/Livewire/x-components how to pass an array?


I have a solution, but I think it's horrid, so I want to ask you if there's a better (or correct) way

I need to create a <select> that I'll use in lots of places around my app. This <select> is what you expect:

<select name="xxx">
    <option value="">...</option>
    @foreach ($equipos as $equipo)
        <option value="{{ $equipo->id }}">
            {{ $equipo->equipo }}
        </option>
    @endforeach
</select>

First Attempt

So, my first attempt was to use an component <x-select-equipos> the component view will be:

<select {{ $attributes->merge(['class' => 'w-full']) }}>
    <option value="">...</option>
    @foreach ($equipos as $equipo)
        <option value="{{ $equipo->id }}">
            {{ $equipo->equipo }}
        </option>
    @endforeach
</select>

So I thought I could call this component like:

<x-select-equipos name="xxx" equipos="{{ $equipos }}" />

But I can't pass the $equipos collection to the component. If I do that this way, it just sends a true

Second try

In my second try I wrote a Livewire component, but it limits me to use Laravel's ORM, so I "rendered" all the <option>s in the component (.php), and just display them in the .blade.php

Another problem this solution has, is that I have to do a call to the database everytime I have to display a <select> and that's suboptimal (more on that below)

My "solution"

I don't like it a bit, but it works, I created a <x-component> like:

<div>
    @php
        use App\Models\Equipo;
        $equipos = Equipo::orderBy('nombre')->get();
    @endphp

    <select {{ $attributes->merge(['class' => 'w-full']) }}>
        <option value="">...</option>
        @foreach ($equipos as $equipo)
            <option value="{{ $equipo->id }}">
                {{ $equipo->nombre }}
            </option>
        @endforeach
    </select>
</div>

There are many things I don't like about the solution, among others is that it has to go to the database every time it has to render an <option>, there's one screen where I have to load 30 selects or so. While the number of records is very small (around 40), I still don't like it, since I find it quite inefficient.

Other possible solution is go back to the Laravel/PHP basics that would be using "partials" or @include, and that would work fine since it will go to the database just once, in fact that was the way I did it in previous versions, I wanted to try the new approaches, either with <x-components> or LiveWire

I'm completely sure there must be a better way.


Solution

  • But I can't pass the $equipos collection to the component. If I do that this way, it just sends a true

    Is $equipos not an eloquent collection? You should be able to pass a collection to your component.

    What about passing a collection, along with the props to specify which field(s) on the models you want to use for the values and labels?

    @props([
        'items', 'value', 'label'
    ])
    
    <select>
        <option value="">{{ __('Please select an option ...') }}</option>
        @foreach ($items as $item)
        <option value="{{ $item->$value }}">{{ $item->$label }}</option>
        @endforeach
    </select>
    

    Then you should be able to call your component as follows (for example):

    <x-select :items="User::all()" value="id" label="email" />