Back to Course |
Livewire 3 From Scratch: Practical Course

Live-Search in the Table

In this lesson, let's add a search by name and category above the table. And here you will see where Livewire shines. After changing any of the inputs, the table will be refreshed without any JS code line.

live search table


Add Search Inputs

First, we need to add inputs above the table. But before that, let's quickly add categories to the products.

database/migrations/xxxx_create_categories_table.php:

Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});

database/migrations/xxxx_add_category_id_to_products_table.php:

Schema::table('products', function (Blueprint $table) {
$table->foreignId('category_id')->after('id');
});

app/Models/Product.php:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Product extends Model
{
// ...
 
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}

database/factories/CategoryFactory.php:

class CategoryFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->words(asText: true),
];
}
}

database/factories/ProductFactory.php:

use App\Models\Category;
 
class ProductFactory extends Factory
{
public function definition(): array
{
$categories = collect(Category::pluck('id'));
 
return [
'category_id' => $categories->random(),
'name' => $this->faker->name(),
'description' => $this->faker->text(50),
];
}
}

database/seeders/DatabaseSeeder.php:

class DatabaseSeeder extends Seeder
{
public function run(): void
{
Category::factory(10)->create();
Product::factory(50)->create();
}
}

We must eager load categories in the Products Livewire component to avoid the N+1 issue. Also, I will refactor it into a separate variable for better readability later.

app/Livewire/Products.php:

class Products extends Component
{
use WithPagination;
 
public function render(): View
{
$products = Product::with('category')
->paginate(10);
 
return view('livewire.products', [
'products' => $products,
]);
}
}

So now we need to show all categories in the search input. For this, we will need a public property and assign categories to it. Assigning needs to be done in the mount method because this part won't be required to be re-rendered after the search.

app/Livewire/Products.php:

use App\Models\Category;
use Illuminate\Support\Collection;
 
class Products extends Component
{
use WithPagination;
 
public Collection $categories;
 
public function mount(): void
{
$this->categories = Category::pluck('name', 'id');
}
 
public function render(): View
{
$products = Product::with('category')
->paginate(10);
 
return view('livewire.products', [
'products' => $products,
]);
}
}

Now we can show everything in the Blade file.

resources/views/livewire/products.blade.php:

<div class="space-y-6">
<div class="space-x-8">
<input type="search" id="search" placeholder="Search...">
 
<select name="category">
<option value="0">-- CHOOSE CATEGORY --</option>
@foreach($categories as $id => $category)
<option value="{{ $id }}">{{ $category }}</option>
@endforeach
</select>
</div>
<div class="min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-200 border">
<thead>
<tr>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Name</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Category</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
<span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Description</span>
</th>
<th class="px-6 py-3 bg-gray-50 text-left">
</th>
</tr>
</thead>
 
<tbody class="bg-white divide-y divide-gray-200 divide-solid">
@forelse($products as $product)
<tr class="bg-white">
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->name }}
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->category->name }}
</td>
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ $product->description }}
</td>
<td>
<a href="#" class="inline-flex items-center px-4 py-2 bg-gray-800 rounded-md font-semibold text-xs text-white uppercase tracking-widest">
Edit </a>
<a href="#" class="inline-flex items-center px-4 py-2 bg-red-600 rounded-md font-semibold text-xs text-white uppercase tracking-widest">
Delete </a>
</td>
</tr>
@empty
<tr>
<td class="px-6 py-4 text-sm" colspan="3">
No products were found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{ $products->links() }}
</div>

search table with inputs


Making Search Work

We have added inputs for the search, so let's make them work. First, we must assign inputs to a property using the wire:model directive. But, with wire:model, we need to use .live so that a server request would be made after the input is updated.

resources/views/livewire/products.blade.php:

<div class="space-y-6">
<div class="space-x-8">
<input wire:model.live="searchQuery" type="search" id="search" placeholder="Search...">
 
<select wire:model.live="searchCategory" name="category">
<option value="0">-- CHOOSE CATEGORY --</option>
@foreach($categories as $id => $category)
<option value="{{ $id }}">{{ $category }}</option>
@endforeach
</select>
</div>
// ...
</div>

For the properties, as you can see, I have chosen searchQuery and searchCategory.

app/Livewire/Products.php:

class Products extends Component
{
use WithPagination;
 
public Collection $categories;
public string $searchQuery = '';
public int $searchCategory = 0;
 
// ...
}

Next, we need to modify the query so that when these properties are set, then the query would get appended with a where statement.

app/Livewire/Products.php:

use Illuminate\Database\Eloquent\Builder;
 
class Products extends Component
{
// ...
 
public function render(): View
{
$products = Product::with('category')
->when($this->searchQuery !== '', fn(Builder $query) => $query->where('name', 'like', '%'. $this->searchQuery .'%'))
->when($this->searchCategory > 0, fn(Builder $query) => $query->where('category_id', $this->searchCategory))
->paginate(10);
 
return view('livewire.products', [
'products' => $products,
]);
}
}

Now you can search and/or select a category, and the products table should be updated instantly.

live search table


Fix Pagination Problem

If you would go to other pages and then would try to search, you would see an empty table. That is because the page isn't resetted. We need to reset pagination when the search property is updated manually. For this, we will use the updating Livewire Lifecycle Hook.

app/Livewire/Products.php:

class Products extends Component
{
// ...
 
public function updating($key): void
{
if ($key === 'searchQuery' || $key === 'searchCategory') {
$this->resetPage();
}
}
 
// ...
}

Persisting Search Query

If you want to persist the search query without using the URL parameter, you can store its value in a session.

app/Livewire/Products.php:

use Livewire\Attributes\Session;
 
class Products extends Component
{
use WithPagination;
 
public Collection $categories;
#[Session]
public string $searchQuery = '';
public int $searchCategory = 0;
 
// ...
}

When the page refreshes, Livewire will get the last value and use it.

You can check other available page navigation methods in the official documentation.