Back to Course |
Livewire VS Vue VS React: Simple Project

Livewire Version

This example will show you how we built the system using Livewire. For this, we will have three Livewire components:

  1. Product List: List of Products with a button to add to the Cart.
  2. Product Filters: Sidebar with filters for Products.
  3. Cart Display: Simply display the number of Products in our Cart.


Installing Laravel Breeze and Livewire

Before we code anything in Livewire, we need to install a few packages:

First, we have to install Breeze:

composer require laravel/breeze --dev

And then we need to run it's install:

php artisan breeze:install blade

Why "blade" stack and not "livewire"? Yes, Laravel Breeze has Livewire as one of the options, but personally, I don't like the Livewire Volt that comes automatically with it. I prefer to use Livewire with Components and without Volt, no need to learn another mini-language on top.

Next, we need to manually install Livewire:

composer require livewire/livewire

Add Future Components to the Dashboard

Once installed, we'll modify our Dashboard template to load our Components:

<x-app-layout>
<x-slot name="header">
<div class="flex flex-grow justify-between items-center">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
{{ __('Products List') }}
</h2>
 
<livewire:cart-counter/>
</div>
</x-slot>
 
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
<div class="flex p-6">
<div class="w-1/3">
<livewire:sidebar/>
</div>
<div class="w-2/3">
<livewire:products/>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

Yes, those components don't exist yet, but I wanted to specifically show you the "overview", first.

Of course, the page will not load at this point, so let's create these components.


Creating Product List Component

This component will be responsible for:

  • Displaying the product list
  • Adding or Removing the Product from our Cart

So, we run:

php artisan make:livewire Products

app/Livewire/Products.php

use App\Models\Cart;
use App\Models\Product;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\On;
use Livewire\Attributes\Url;
use Livewire\Component;
 
class Products extends Component
{
public array $cartProducts = [];
 
#[Url]
public array $selected = [
'prices' => [],
'categories' => [],
'manufacturers' => []
];
 
public function render(): View
{
$products = Product::withFilters(
$this->selected['prices'],
$this->selected['categories'],
$this->selected['manufacturers']
)->get();
 
$this->cartProducts = Cart::pluck('product_id')->unique()->toArray();
 
return view('livewire.products', compact('products'));
}
 
#[On('updatedSidebar')]
public function setSelected($selected): void
{
$this->selected = $selected;
}
 
public function addOrRemoveFromCart(int $productId): void
{
if (in_array($productId, $this->cartProducts, true)) {
Cart::where('product_id', $productId)->delete();
 
unset($this->cartProducts[$productId]);
} else {
Cart::create(['product_id' => $productId]);
 
$this->cartProducts[] = $productId;
}
 
$this->dispatch('updateCart');
}
}

Here's the exact breakdown of what we did:

  • The $cartProducts property is used to know which products were already added to the Cart to show the Remove button
  • The $selected property is used to retrieve all selected filters (more on them in our next Component)
  • render() just collects the data needed and returns a View
  • setSelected() is an event listener that reacts to events from the Product Filter component
  • The addOrRemoveFromCart() function handles the button click on each product.

Next, we need to add the View code:

resources/views/livewire/products.blade.php

<div class="flex flex-wrap">
@foreach($products as $product)
<div class="w-1/3 p-3">
<div class="rounded-md">
<a href="#"><img src="http://placehold.it/700x400" alt=""></a>
<div class="mt-3">
<a href="#" class="text-2xl text-indigo-500 hover:underline">{{ $product->name }}</a>
</div>
<h5 class="mt-3">$ {{ number_format($product->price, 2) }}</h5>
<p class="mt-3">{{ $product->description }}</p>
 
<div class="mt-4 border-t pt-6">
@if (! in_array($product->id, $cartProducts))
<a wire:click.prevent="addOrRemoveFromCart({{ $product->id }})"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
href="#">
Add to Cart
</a>
@else
<a wire:click.prevent="addOrRemoveFromCart({{ $product->id }})"
class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
href="#">
Remove from Cart
</a>
@endif
</div>
</div>
</div>
@endforeach
</div>

In this case, it's simple to use foreach to render our list. Then, we have a button to add/remove each product from the Cart.


Creating Product Filters Component

Now, we need a way to filter the products:

php artisan make:livewire Sidebar

app/Livewire/Sidebar.php

use App\Models\Category;
use App\Models\Manufacturer;
use App\Services\PriceService;
use Livewire\Attributes\Url;
use Livewire\Component;
use Illuminate\Contracts\View\View;
 
class Sidebar extends Component
{
#[Url]
public array $selected = [
'prices' => [],
'categories' => [],
'manufacturers' => []
];
 
public function render(PriceService $priceService): View
{
$prices = $priceService->getPrices(
[],
$this->selected['categories'],
$this->selected['manufacturers']
);
 
$categories = Category::withCount(['products' => function ($query) {
$query->withFilters(
$this->selected['prices'],
[],
$this->selected['manufacturers']
);
}])
->get();
 
$manufacturers = Manufacturer::withCount(['products' => function ($query) {
$query->withFilters(
$this->selected['prices'],
$this->selected['categories'],
[]
);
}])
->get();
 
return view('livewire.sidebar', compact('prices', 'categories', 'manufacturers'));
}
 
public function updatedSelected(): void
{
$this->dispatch('updatedSidebar', $this->selected);
}
}

We can break this down into the following:

  • The $selected property is used to store the filters (they will also be stored in the URL as Query param)
  • render() retrieves available filters and returns the View
  • updateSelected() dispatches an event we catch in our Product List component.

Last, we need a view for it:

resources/views/livewire/sidebar.blade.php

<div class="col-lg-3 mb-4">
<h1 class="mt-4 text-4xl">Filters</h1>
 
<h3 class="mt-2 mb-1 text-3xl">Price</h3>
@foreach($prices as $index => $price)
<div>
<input type="checkbox" id="price{{ $index }}" value="{{ $index }}" wire:model.live="selected.prices">
<label for="price{{ $index }}">
{{ $price['name'] }} ({{ $price['products_count'] }})
</label>
</div>
@endforeach
 
<h3 class="mt-2 mb-1 text-3xl">Categories</h3>
@foreach($categories as $index => $category)
<div class="form-check">
<input type="checkbox" id="category{{ $index }}" value="{{ $category->id }}"
wire:model.live="selected.categories">
<label for="category{{ $index }}">
{{ $category['name'] }} ({{ $category['products_count'] }})
</label>
</div>
@endforeach
 
<h3 class="mt-2 mb-1 text-3xl">Manufacturers</h3>
@foreach($manufacturers as $index => $manufacturer)
<div class="form-check">
<input type="checkbox" id="manufacturer{{ $index }}" value="{{ $manufacturer->id }}"
wire:model.live="selected.manufacturers">
<label for="manufacturer{{ $index }}">
{{ $manufacturer['name'] }} ({{ $manufacturer['products_count'] }})
</label>
</div>
@endforeach
</div>

In this case, we have a few simple foreach loops that render different items.

These items have the wire:model.live attribute to set filters immediately.


Creating Cart Component

This component will be the simplest one - just a basic text with a count:

php artisan make:livewire CartCounter

app/Livewire/CartCounter.php

use App\Models\Cart;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\On;
use Livewire\Component;
 
class CartCounter extends Component
{
public function render(): View
{
return view('livewire.cartCounter', [
'cartAmount' => Cart::count(),
]);
}
 
#[On('updateCart')]
public function refreshComponent(): void
{
$this->dispatch('$refresh');
}
}

The only thing worth noting is that we have a listener for the updateCart event that comes from our Product List button.

And the view:

resources/views/livewire/cartCounter.blade.php

<div class="px-4 py-3 leading-normal text-blue-700 bg-blue-100 rounded-lg text-right w-1/3">
<i class="fa fa-shopping-cart"></i>
Cart ({{ $cartAmount }})
</div>

That's it for our counter.

And yeah, the whole application is now working! You can filter the products via the left sidebar and add any product to cart, looking at the Cart number going up!


Ok, time to compare the experience of building the same functionality with Vue.js and Inertia.

You can find this code for this lesson in a separate branch on GitHub