Skip to main content

Black Friday 2025! Only until December 1st: coupon FRIDAY25 for 40% off Yearly/Lifetime membership!

Read more here

In this lesson, we will add live search functionality to the table.

filtered products

First, we will get a list of categories and countries. For this, we will need public properties for both and we will assign them in the mount() method...

The Full Lesson is Only for Premium Members

Want to access all of our courses? (29 h 14 min)

You also get:

54 courses
Premium tutorials
Access to repositories
Private Discord
Get Premium for $129/year or $29/month

Already a member? Login here

Comments & Discussion

SP
Steve Purcell ✓ Link copied!

Can you further explain the need to multiply by 100 in the product price search? It is displaying the price stored in the DB divided by 100. The search works fine on the price that is displayed however because it is dividing by 100 in the search.

PK
Povilas Korop ✓ Link copied!

Not sure if I understand the question. We need to search by price in the DATABASE, which is in cents, that's why we need to multiply the search parameters by 100 to have them in cents, to match the database records.

N
Nouar ✓ Link copied!

Very nicen specialy the search parts

A
augusto-dmh ✓ Link copied!

my five cents: there are lots of way to store money in the database, but perhaps the most common - convention adopted by famous examples such as Stripe - is storing in the database as cents, due to the problem around float-poiting numbers: compilers treat them differently and they've got thus no quite precision. Integers are more reliable

RB
Rajesh Budhathoki ✓ Link copied!

I found the query is bit complex. Can't we simplify it by using Eloquent ORM?

PK
Povilas Korop ✓ Link copied!

Not much to simplify, I think, as the task itself is pretty complex. But you can give it a try and post your suggestions :)

E
Emruardo ✓ Link copied!

It is not really an improvement but my query looks like this. just got rid of the "foreach" loop. "to" is set to max price in products. If "to" is 0, query doesn't return any product. It is not bug but feature ;)


// ...
public function mount(): void
{
	 // ...
	$this->searchColumns["price"] = [0, Product::max('price') / 100];
}
// ...
public function render()
{
	$products = Product::query()
		->select(['products.*', 'countries.id as countryId', 'countries.name as countryName',])
		->join('countries', 'countries.id', '=', 'products.country_id')
		->whereBetween(
				'products.price',
				[
						floatval($this->searchColumns['price'][0]) * 100,
						floatval($this->searchColumns['price'][1]) * 100
				]
		)
		->with('categories')
		->where('products.name', 'LIKE', '%' . $this->searchColumns['name'] . '%')
		->where('products.description', 'LIKE', '%' . $this->searchColumns['description'] . '%')
		->when($this->searchColumns['category_id'], function ($query) {
				$query->whereRelation('categories', 'id', $this->searchColumns['category_id']);
		})
		->when($this->searchColumns['country_id'], function ($query) {
				$query->whereRelation('country', 'id', $this->searchColumns['country_id']);
		});

	return view('livewire.products-list', [
		'products' => $products->paginate(10),
	]);
}
ED
Emre Dikmen ✓ Link copied!

I simplified the code by moving the functionality to local scopes.


  #[Layout('layouts.app')]
    public function render()
    {
        $products = Product::query()
            ->select(['products.*', 'countries.id as countryId', 'countries.name as countryName'])
            ->join('countries', 'countries.id', '=', 'products.country_id')
            ->with('categories')
            ->filterByName($this->searchColumns['name'])
            ->filterByPrice($this->searchColumns['price'][0], $this->searchColumns['price'][1])
            ->filterByCategory($this->searchColumns['category_id'])
            ->filterByCountry($this->searchColumns['country_id'])
            ->paginate();

        return view('livewire.products.products-lists', [
            'products' => $products
        ]);
    }
ED
Emre Dikmen ✓ Link copied!
 public function scopeFilterByName($query, $name)
    {
        return $query->when($name, fn($query) => $query->where('products.name', 'LIKE', '%' . $name . '%'));
    }

    public function scopeFilterByPrice($query, $min, $max)
    {
        return $query->when(is_numeric($min), fn($query) => $query->where('price', '>=', $min * 100))
            ->when(is_numeric($max), fn($query) => $query->where('price', '<=', $max * 100));
    }

    public function scopeFilterByCategory($query, $categoryId)
    {
        return $query->when($categoryId, fn($query) => $query->whereRelation('categories','id', $categoryId));
    }

    public function scopeFilterByCountry($query, $countryId)
    {
        return $query->when($countryId, fn($query) => $query->whereRelation('country','id', $countryId));
    }
R
Robert ✓ Link copied!

When I search by category I got the following error

select count(*) as aggregate from `products` inner join `countries` on `countries`.`id` = `products`.`country_id` where `products`.`name` LIKE %volup% and exists (select * from `categories` inner join `category_product` on `categories`.`id` = `category_product`.`category_id` where `products`.`id` = `category_product`.`product_id` and `id` = 7)

Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous, I am on Laravel 10

N
Nerijus ✓ Link copied!

Post your full query formatted in php markdown

M
muuucho ✓ Link copied!

Solution: replace the last 'id' in the following query with 'categories.id'

$products = Product::query()
            ->select(['products.*', 'countries.id as countryId', 'countries.name as countryName',])
            ->join('countries', 'countries.id', '=', 'products.country_id')
            ->with('categories');

        foreach ($this->searchColumns as $column => $value) {
            if (!empty($value)){
                $products->when($column == 'name', fn($products) => $products->where('products.'.$column, 'LIKE', '%'.$value.'%' ));
                $products->when($column == 'description', fn($products) => $products->where('products.'.$column, 'LIKE', '%'.$value.'%' ));
                $products->when($column == 'country_id', fn($products) => $products->whereRelation('country', 'id', $value));
                $products->when($column == 'category_id', fn($products) => $products->whereRelation('categories', 'id', $value));
            }
        }
M
Márlon ✓ Link copied!

Hello all! I not think it is a improvement, but I like to have a message when my filter result in no records found.

@forelse($products as $product)
    <tr class="bg-white">
        <td class="px-4 py-2 text-sm leading-5 text-gray-900 whitespace-no-wrap">
            <input type="checkbox" value="{{ $product->id }}" wire:model="selected">
        </td>
        <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
            {{ $product->name }}
        </td>
        <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
            @foreach($product->categories as $category)
                <span class="px-2 py-1 text-xs text-indigo-700 bg-indigo-200 rounded-md">{{ $category->name }}</span>
            @endforeach
        </td>
        <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
            {{ $product->countryName }}
        </td>
        <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
            ${{ number_format($product->price / 100, 2) }}
        </td>
        <td>
            <a class="inline-flex items-center px-4 py-2 text-xs font-semibold tracking-widest text-white uppercase bg-gray-800 rounded-md border border-transparent hover:bg-gray-700">
                Edit
            </a>
            <button class="px-4 py-2 text-xs text-red-500 uppercase bg-red-200 rounded-md border border-transparent hover:text-red-700 hover:bg-red-300">
                Delete
            </button>
        </td>
    </tr>
@empty
    <tr class="bg-gray-300">
        <td colspan="6" class="text-center">No results found!</td>
    </tr>
@endforelse
</tbody>
M
muuucho ✓ Link copied!

Tips: If you save your file in views/livewire dont' dorget to include livewire in your includes:

@include('livewire.sort-' . $sortDirection)
@include('livewire.sort')
M
muuucho ✓ Link copied!

A problem that I have is that when I type something in any search field the autofocus is gone (and so is the text coursor) as soon as the search updates, so I have to click the field again to be able to continue to type in that field. Does anyone experience the sam or have I missed something?

N
Nerijus ✓ Link copied!

Don't have such problem

S
sergioarc ✓ Link copied!

Hello...

Nothing happens when I select from the dropdown or type something on the search inputs... All previuos lessons are working as expected, but not this one...

Here is the code:

--- Component namespace App\Livewire;

use App\Models\Category; use App\Models\Country; use App\Models\Product; use Livewire\Component; use Livewire\WithPagination; use Illuminate\Contracts\View\View;

class ProductList extends Component { use WithPagination;

public array $categories = [];

public array $countries = [];

public array $searchColumns = [
    'name' => '',
    'price' => ['', ''],
    'description' => '',
    'category_id' => 0,
    'country_id' => 0,
];

public function mount(): void
{
    $this->categories = Category::pluck('name', 'id')->toArray();
    $this->countries = Country::pluck('name', 'id')->toArray();
}

public function render(): View
{
    $products = Product::query()
        ->select(['products.*', 'countries.id as countryId', 'countries.name as countryName',])
        ->join('countries', 'countries.id', '=', 'products.country_id')
        ->with('categories');

    foreach ($this->searchColumns as $column => $value) {
        if (!empty($value)) {
            $products->when($column == 'price', function ($products) use ($value) {
                if (is_numeric($value[0])) {
                    $products->where('products.price', '>=', $value[0] * 100);
                }
                if (is_numeric($value[1])) {
                    $products->where('products.price', '<=', $value[1] * 100);
                }
            })
            ->when($column == 'category_id', fn($products) => $products->whereRelation('categories', 'id', $value))
            ->when($column == 'country_id', fn($products) => $products->whereRelation('country', 'id', $value))
            ->when($column == 'name', fn($products) => $products->where('products.' . $column, 'LIKE', '%' . $value . '%'));
        }
    }

    return view('livewire.product-list', [
        'products' => $products->paginate(10),
    ]);
}

}

--- View

{{ __('Productos') }}

<div class="py-12">
    <div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
        <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
            <div class="p-6 bg-white border-b border-gray-200">

                <div class="mb-4">
                    <div class="mb-4">
                        <a class="inline-flex items-center px-4 py-2 text-xs font-semibold tracking-widest text-white uppercase bg-gray-800 rounded-md border border-transparent hover:bg-gray-700">
                            Crear un Producto
                        </a>
                    </div>
                </div>

                <div class="overflow-hidden overflow-x-auto mb-4 min-w-full align-middle sm:rounded-md">
                    <table class="min-w-full border divide-y divide-gray-200">
                        <thead>
                        <tr>
                            <th class="px-6 py-3 text-left bg-gray-50">
                            </th>
                            <th class="px-6 py-3 text-left bg-gray-50">
                                <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Name</span>
                            </th>
                            <th class="px-6 py-3 text-left bg-gray-50">
                                <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Categories</span>
                            </th>
                            <th class="px-6 py-3 text-left bg-gray-50">
                                <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Country</span>
                            </th>
                            <th class="px-6 py-3 w-32 text-left bg-gray-50">
                                <span class="text-xs font-medium tracking-wider leading-4 text-gray-500 uppercase">Price</span>
                            </th>
                            <th class="px-6 py-3 text-left bg-gray-50">
                            </th>
                        </tr>
                        <tr>
                            <td></td>
                            <td class="px-2 py-2">
                                <input wire:model="searchColumns.name" type="text" placeholder="Search..."
                                       class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
                            </td>
                            <td class="px-2 py-1">
                                <select wire:model="searchColumns.category_id"
                                        class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                                    <option value="">-- choose category --</option>
                                    @foreach($categories as $id => $category)
                                        <option value="{{ $id }}">{{ $category }}</option>
                                    @endforeach
                                </select>
                            </td>
                            <td class="px-2 py-1">
                                <select wire:model="searchColumns.country_id"
                                        class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50">
                                    <option value="">-- choose country --</option>
                                    @foreach($countries as $id => $country)
                                        <option value="{{ $id }}">{{ $country }}</option>
                                    @endforeach
                                </select>
                            </td>
                            <td class="px-2 py-1 text-sm">
                                <div>
                                    From
                                    <input wire:model="searchColumns.price.0" type="number"
                                           class="mr-2 w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
                                </div>
                                <div>
                                    to
                                    <input wire:model="searchColumns.price.1" type="number"
                                           class="w-full text-sm rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
                                </div>
                            </td>
                            <td></td>
                        </tr>
                        </thead>

                        <tbody class="bg-white divide-y divide-gray-200 divide-solid">
                        @foreach($products as $product)
                            <tr class="bg-white">
                                <td class="px-4 py-2 text-sm leading-5 text-gray-900 whitespace-no-wrap">
                                    <input type="checkbox" value="{{ $product->id }}" wire:model="selected">
                                </td>
                                <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
                                    {{ $product->name }}
                                </td>
                                <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
                                    @foreach($product->categories as $category)
                                        <span class="px-2 py-1 text-xs text-indigo-700 bg-indigo-200 rounded-md">{{ $category->name }}</span>
                                    @endforeach
                                </td>
                                <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
                                    {{ $product->countryName }}
                                </td>
                                <td class="px-6 py-4 text-sm leading-5 text-gray-900 whitespace-no-wrap">
                                    ${{ number_format($product->price / 100, 2) }}
                                </td>
                                <td>
                                    <a class="inline-flex items-center px-4 py-2 text-xs font-semibold tracking-widest text-white uppercase bg-gray-800 rounded-md border border-transparent hover:bg-gray-700">
                                        Edit
                                    </a>
                                    <button class="px-4 py-2 text-xs text-red-500 uppercase bg-red-200 rounded-md border border-transparent hover:text-red-700 hover:bg-red-300">
                                        Delete
                                    </button>
                                </td>
                            </tr>
                        @endforeach
                        </tbody>
                    </table>
                </div>

                {{ $products->links() }}

            </div>
        </div>
    </div>
</div>
N
Nerijus ✓ Link copied!

Sorry, but if you wan't help edit your message to put code in a gist instead of hear. Other important question are you using livewire v2 or v3? They have some minor but very important differences. The most important is how requests are being sent to the server.

IC
Ikaro Campos Laborda ✓ Link copied!

This Lesson does not seem to be working when using Livewire 3.4. I went through the docs but couldn't find something that helps

N
Nerijus ✓ Link copied!

Because this course was made before v3.

IC
Ikaro Campos Laborda ✓ Link copied!

@Nerijus, do you know a way to make it work on Livewire 3, or at least point me where in the docs should I go?

N
Nerijus ✓ Link copied!

its not just about the docs but you should know how livewire works. Povilas approved to update this course for livewire v3. When it will happen cannot say. Would guess next month.

PK
Povilas Korop ✓ Link copied!

Hi Ikaro, we updated the course text in full to Livewire 3, including the repository. Good job, Nerijus.

M
McNab ✓ Link copied!

Not sure if adding the category_product pivot table happened when the course was updated to Livewire v3 but I had to modify the query to include that table in order to get the category search to work;

foreach ($this->searchColumns as $column => $value) {
    if (!empty($value)) {
        $products->when($column == 'price', function ($products) use ($value) {
            if (is_numeric($value[0])) {
                $products->where('products.price', '>=', $value[0] * 100);
            }
            if (is_numeric($value[1])) {
                $products->where('products.price', '<=', $value[1] * 100);
            }
        })
        ->when($column == 'category_id', function ($products) use ($value) {
            $products->join('category_product', 'products.id', '=', 'category_product.product_id')
                     ->where('category_product.category_id', $value);
        })
        ->when($column == 'country_id', fn($products) => $products->whereRelation('country', 'id', $value))
        ->when($column == 'name', fn($products) => $products->where('products.' . $column, 'LIKE', '%' . $value . '%'));
    }
} 

We'd Love Your Feedback

Tell us what you like or what we can improve

Feel free to share anything you like or dislike about this page or the platform in general.