Installation:
composer require mitratek/livewire-select
npm install --save-dev @alpinejs/collapse
Ensure alpinejs and tailwindcss are already configured in your Laravel project.
Publish Assets (if needed):
npm run dev
The package includes Tailwind/Alpine-based styling out-of-the-box.
First Use Case: Create a Livewire component for a searchable dropdown (e.g., user role selection):
// app/Http/Livewire/SelectRole.php
use Livewire\Component;
use Mitratek\LivewireSelect\LivewireSelect;
class SelectRole extends Component
{
use LivewireSelect;
public $role_id;
public function render()
{
return view('livewire.select-role');
}
}
<!-- resources/views/livewire/select-role.blade.php -->
<livewire-select
name="role_id"
model="App\Models\Role"
search="['name', 'description']"
show="{name} ({id})"
value="id"
placeholder="Select a role..."
/>
Dynamic Data Fetching:
search to define which model attributes trigger re-fetching:
search="['name', 'email', 'slug']" // Searches across these fields
paginate option:
<livewire-select
name="user_id"
model="App\Models\User"
search="['name']"
paginate="10"
/>
Custom Display Logic:
show attribute to format output:
show="{name} (ID: {id})" // Custom template for displayed items
<livewire-select
name="product_id"
model="App\Models\Product"
show="<span class='font-bold'>{name}</span> - ${price}"
/>
Integration with Forms:
<form wire:submit="save">
<livewire-select
name="category_id"
model="App\Models\Category"
wire:model="category_id"
/>
<button type="submit">Save</button>
</form>
protected $rules = ['role_id' => 'required|exists:roles,id'];
Async Operations:
debounce to optimize API calls:
<livewire-select
name="search_term"
model="App\Models\SearchResult"
search="['query']"
debounce="500" // 500ms delay
/>
Nested Relationships:
with:
<livewire-select
name="post_id"
model="App\Models\Post"
with="['author']"
show="{title} by {author.name}"
/>
Model Requirements:
toSearchableArray() or use default search fields.class User extends Model
{
public function toSearchableArray()
{
return ['name', 'email', 'custom_field'];
}
}
Alpine JS Conflicts:
x-data) interfere, wrap the component in a unique Alpine scope:
<div x-data="{ open: false }">
<livewire-select ... />
</div>
Tailwind Styling:
tailwind.config.js:
module.exports = {
theme: {
extend: {
colors: {
'select-bg': '#f8fafc',
'select-border': '#e2e8f0',
}
}
}
}
<livewire-select
name="status"
class="select-custom"
style="--select-bg: #e2e8f0;"
/>
Performance with Large Datasets:
paginate and loadMore events:
<livewire-select
name="user_id"
model="App\Models\User"
paginate="20"
@load-more="loadMoreUsers"
/>
public function scopeSearch($query, $search)
{
return $query->where('name', 'like', "%{$search}%")
->remember(60); // Cache for 60 mins
}
Debugging:
protected static function booted()
{
static::addGlobalScope('logSearch', function (Builder $builder) {
if (app()->environment('local')) {
$builder->toSql(); // Log SQL in Laravel logs
}
});
}
F12) and inspect the wire:ignore element for Alpine/Livewire conflicts.Reusable Components:
// app/Http/Livewire/BaseSelect.php
use Mitratek\LivewireSelect\LivewireSelect;
class BaseSelect extends Component
{
use LivewireSelect;
public $selectedId;
public $model;
public $searchFields;
public $displayFields;
public function mount($model, $searchFields, $displayFields)
{
$this->model = $model;
$this->searchFields = $searchFields;
$this->displayFields = $displayFields;
}
public function render()
{
return view('livewire.base-select');
}
}
<!-- resources/views/livewire/base-select.blade.php -->
<livewire-select
name="selectedId"
model="{{ $model }}"
search="{{ json_encode($searchFields) }}"
show="{{ $displayFields }}"
/>
Multi-Select:
multiple attribute (requires custom Alpine logic):
<livewire-select
name="tag_ids[]"
model="App\Models\Tag"
multiple
search="['name']"
show="{name}"
/>
livewire-select-multiple.Accessibility:
<livewire-select
name="accessibility_id"
aria-label="Select an accessible option"
aria-describedby="select-help"
/>
wire:model is properly labeled:
<label for="role_id">User Role</label>
<livewire-select
name="role_id"
id="role_id"
...
/>
Testing:
call():
public function test_search_returns_results()
{
$component = new SelectRole();
$component->search('admin'); // Trigger search
$this->assertCount(1, $component->getSearchResults());
}
$mockModel = Mockery::mock('App\Models\Role');
$mockModel->shouldReceive('search')->andReturn([new Role(['name' => 'Test'])]);
How can I help you explore Laravel packages today?