Courses

Livewire 3 From Scratch: Practical Course

Table with Pagination

Summary of this lesson:
- Setting up products database structure
- Moving table to Livewire component
- Implementing pagination with WithPagination trait
- Managing URL parameters for pagination

In this section, let's dive into more complex and practical stuff: we will create a CRUD with Livewire. Let's start by creating a table in Laravel, moving it to Livewire, and adding pagination to the table.


Initial Structure

For the initial Laravel project, we will have a Product Model and a page to list all the products.

database/migrations/xxxx_create_products_table.php:

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

app/Models/Product.php:

use Illuminate\Database\Eloquent\Factories\HasFactory;
 
class Product extends Model
{
use HasFactory;
 
protected $fillable = [
'name',
'description',
];
}

database/factories/ProductFactory.php:

class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => $this->faker->name(),
'description' => $this->faker->text(50),
];
}
}

database/seeders/DatabaseSeeder.php:

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

Don't forget to run Seeder when migrating database php artisan migrate --seed.

app/Http/Controllers/ProductController.php:

use App\Models\Product;
use Illuminate\Contracts\View\View;
 
class ProductController extends Controller
{
public function index(): View
{
$products = Product::all();
 
return view('products.index', compact('products'));
}
}

routes/web.php:

Route::get('products', [\App\Http\Controllers\ProductController::class, 'index']);

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

<x-app-layout>
 
// ... layout header code
 
<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">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->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>
 
// ... layout footer code
 
</x-app-layout>

Notice: In this tutorial, we use the Laravel Breeze layout with x-app-layout Blade component, but feel free to use any other layout. Read more in the docs.

After visiting the /products page, you should see a table with the list of products.

products list


Move to Livewire Component

Now let's move the table to the Livewire component. First, we need a component.

php artisan make:livewire Products

Next, the whole table code move into the Livewire components Blade file and instead call the Products Livewire component.

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

<x-app-layout>
 
// ... layout header code
 
<div class="min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-200 border">
// ...
</table>
</div>
<livewire:products />
 
// ... layout footer code
 
</x-app-layout>

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

<div class="min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-200 border">
// ...
</table>
</div>

We must pass the products list to the view in the Livewire components class.

app/Livewire/Products.php:

use App\Models\Product;
use Illuminate\Contracts\View\View;
 
class Products extends Component
{
public function render(): View
{
return view('livewire.products', [
'products' => Product::all(),
]);
}
}

After refreshing the page visually, there shouldn't be any changes, but now the table is in the Livewire component.


Adding Pagination

To paginate Products, there is no difference from how you would do it in the Controller. We use the paginate method and call the links method in the Blade to show pagination links.

app/Livewire/Products.php:

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

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

<div class="space-y-6">
<div class="min-w-full align-middle">
<table class="min-w-full divide-y divide-gray-200 border">
// ...
</table>
</div>
{{ $products->links() }}
</div>

Remember, Livewire needs to have one root HTML element. That's why here we wrapped everything with a <div> element.

If you visit the page now, you will see an error.

paginator error

We need to add a WithPagination trait to use pagination with Livewire.

app/Livewire/Products.php:

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

And now we have a working pagination.

livewire pagination

If you want to remove the ?page from the URL, this can be done by adding an additional Livewire\WithoutUrlPagination trait.

app/Livewire/Products.php:

use Livewire\WithoutUrlPagination;
 
class Products extends Component
{
use WithPagination;
use WithoutUrlPagination;
 
public function render(): View
{
return view('livewire.products', [
'products' => Product::paginate(10),
]);
}
}
Previous: Lazy Loading Components
avatar

Hi I got an error when I use the pagination, I follow all the steps. but still got an error. Method Illuminate\Database\Eloquent\Collection::links does not exist.

Does anyone encounter the same issue with mine?

avatar

that has happened to me when the paginate method isn't being used, check that you did replace this in the render method on the component : 'products' => Product::all().
with 'products' => Product::paginate(10),

avatar

I did that too. I don't know why I'm having this kind of issue.

avatar

Base on the example, it doesn't have the mount() method, if I don't put the mount() method in my component then there's another an issue which is Illuminate\Database\Eloquent\Collection::links does not exist.. I'm confused. I hope somebody can help me on this.

avatar
You can use Markdown
avatar

For clarity; I think that it is assumed that you add a template layout to the project. Here is how to do it: Livewire docs says:

"Remember that full-page components will use your application's layout, typically defined in the resources/views/components/layouts/app.blade.php file."

So, I creted the layout in resources/views/components/layouts/app.blade.php: (If you place it or name it different, you must change 'layout' => 'components.layouts.app' in config/livewire.php)

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
    {{ $slot }}
</body>
</html>

As shown, I am calling Tailwind from Cloudflare CDN. You migth want to intall tailwind directly to your project instead.

Then, I changed my "resources/views/products/index.blade.php" so that it will be njected into the layout as $slot above:

<x-layouts.app>
    <livewire:products />
</x-layouts.app>
avatar

This course isn aboy laravel it's about livewire. So how to install tailwind or use blade components you should know. But in the first lesson before starting it is briefly mentioned

avatar

But you give the code for the model, factory and seeder. In my opinion that is more basic Laravel than components. If you disagree feel free to remove the post, no problem.

But be aware that if someone runs this lesson on top of Jetstream, (wich is mentioned in the first lesson) then the layout must be moved into Components and the view that is called in app/View/Components/AppLayout.php must be changed to 'components.layouts.app' since Livewire expects to find the layout there. If this isn't changed, there will be a conflict between Livewire and Jetstream. Maybe this is obvious for most students, but it wasn't for me. I am not a Laravel expert, but I am still eager to learn Livewire. Thank you for beeing so early with this v3 course!

avatar

Well, if they are using Jetstream then they should first follow jetstream docs right? As I said this course isn't about using Breeze or Jetstream. It's only about Livewire.

avatar

That is not correct. You said to me that this course isn't about Laravel, (But You wrote:"This course isn aboy laravel it's about livewire." So, nothing about Breeze/Jetstream) then I showed you some example where you are teaching Laravel stuff, this said as an explanation why I thought it was welcome with other Laravel tips that could be useful during this course. Then I followed up with some friendly tips for students that might use Jetstream (that is suggested in the first lesson). Also this tips you turn down. Let's hope that you will only have Laravel Ninjas as paying clients in the future.

avatar

Jetsteam or breeze is everyones personal preference. If someone ones to use they can. But this course is purely about livewire. When using other then in the course of course there might be something different. I guess we are miscommunicati

avatar

Of course, I am entirely aware that this is a Livewire course and not a Laravel course. However, Livewire depends on Laravel and if someone runs into a Laravel issue while attempting to make this course, it shouldn't be a problem to ask these questions here? Or even to give tips about struggles one has run into? If that is not your opinion, I think you should publish a working repo somewhere.

I think that the course is good as it is (without repo), so please, let the community discuss what they find cumbersome or even give some tips, be it Livewire, laravel etc.

I hope that this course gets wideley attention with a vibrant community. Cheers!

avatar

@muuucho thanks for the comment, and you're partly right. It's always an ongoing question for us - how much ADDITIONAL info to include on a certain course, and how much to assume that students would know upfront.

We're focused on Livewire in this case, assuming that students would "know everything else" but often that's not the case.

I guess the best middle ground is to at least mention the fundamentals and put the links to them. I will go through this course again and take a look where such "knowledge gaps" could be improved with extra few paragraphs or links to the docs.

avatar
You can use Markdown
avatar
  • Illuminate\Contracts\View\View;
  • Illuminate\View\View; Both package is same?? if they are different -- then which one should be used where.....
avatar

One of them is Contract (interface), another one it the Implementation of that Contract. In most cases, it doesn't matter which you use, but the official Laravel Bootstamp uses Illuminate\View\View

avatar
You can use Markdown
avatar

I have added a "View" button in my table. It has a link that hits a route that goes to a new file named app\Http\Livewire\ProductsView.php I like to fetch the post there, but with below code I get the error "Cannot assign Illuminate\Database\Eloquent\Collection to property App\Livewire\ProductsView::$product of type App\Models\Product". Any idea what's wrong?

<?php

namespace App\Livewire;
use Livewire\Component;
use App\Models\Product;

class ProductsView extends Component
{
    public Product $product;

    public function mount($product)
    {
        $this->product = Product::with(['categories'])->where('id', '=', $product->id)->get();
        dd($this->product);
    }

    public function render()
    {
        return view('livewire.products-view');
    }
}

avatar

It's because you are using get() which returns a collection and you are type hinting it to a Product model. Changet get() to a first() and it will return a single first model.

avatar
You can use Markdown
avatar

Now that the data is being fetched from the Livewire Component instead, aren't we supposed to also update our controller from:

class ProductController extends Controller
{
    public function index(): View
    {
        $products = Product::all();
 
        return view('products.index', compact('products'));
    }
}

to

class ProductController extends Controller
{
    public function index(): View
    {
        return view('products.index');
    }
}

Or am I missing something?

👍 1
avatar

in a real project yes

avatar
You can use Markdown
avatar
You can use Markdown