Reputation: 183
So I am being given this error when trying to paginate my data and send it in to a view through a livewire component.
I am trying to get all posts and display at max 5 posts per page using pagination in laravel.
Livewire version: 2.3.1
Laravel version: 8.13.0
Error:
Livewire\Exceptions\PublicPropertyTypeNotAllowedException
Livewire component's [user-posts] public property [posts] must be of type: [numeric, string, array, null,
or boolean]. Only protected or private properties can be set as other types because JavaScript doesn't
need to access them.
My Component:
<?php
namespace App\Http\Livewire;
use Livewire\Component;
use App\Models\Post;
use Livewire\WithPagination;
class UserPosts extends Component
{
use WithPagination;
public $posts;
public $type;
protected $listeners = ['refreshPosts'];
public function delete($postId)
{
$post = Post::find($postId);
$post->delete();
$this->posts = $this->posts->except($postId);
}
public function render()
{
if($this->type == 'all')
$this->posts = Post::latest()->paginate(5);
else if($this->type == 'user')
$this->posts = Post::where('user_id',Auth::id())->latest()->paginate(5);
return view('livewire.user-posts', ['posts' => $this->posts]);
}
}
My Blade:
<div wire:poll.5s>
@foreach($posts as $post)
<div style="margin-top: 10px">
<div class="post">
<div class="flex justify-between my-2">
<div class="flex">
<h1>{{ $post->title }}</h1>
<p class="mx-3 py-1 text-xs text-gray-500 font-semibold"
style="margin: 17px 0 16px 40px">{{ $post->created_at->diffForHumans() }}</p>
</div>
@if(Auth::id() == $post->user_id)
<i class="fas fa-times text-red-200 hover:text-red-600 cursor-pointer"
wire:click="delete({{$post->id}})"></i>
@endif
</div>
<img src="{{ asset('image/banner.jpg') }}" style="height:200px;"/>
<p class="text-gray-800">{{ $post->text }}</p>
@livewire('user-comments', [
'post_id' => $post->id,
'type' => 'all'
],
key($post->id)
)
</div>
</div>
@endforeach
{{ $posts->links() }}
</div>
Upvotes: 1
Views: 13719
Reputation: 183
$posts should not be declared as a property in the Livewire component class, you passed the posts with the laravel view() helpers as data.
Remove the line
public $posts;
And replace $this->posts by $posts in the render function:
public function render()
{
if($this->type == 'all')
$posts = Post::latest()->paginate(5);
else if($this->type == 'user')
$posts = Post::where('user_id',Auth::id())->latest()->paginate(5);
return view('livewire.user-posts', ['posts' => $posts]);
}
}
Upvotes: 13
Reputation: 1
this is my solution for this problem. Basically, I do not set paginate to livewire component property. Instead, I have property for filter and sort. Then before render, I run query to get data. In this product list component, I filter by category to get product and sort it by title, price or created_at.
This is my livewire ProductList
component:
<?php
namespace App\Livewire;
use App\Models\Product;
use App\Models\ProductCategory;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
class ProductList extends Component
{
use WithPagination;
public $categoryId = 1;
public $sort = 'latest';
public $sortColumn = 'created_at';
public $sortOrder = 'desc';
#[Computed]
public function categories()
{
return ProductCategory::select('id', 'name')->get();
}
public function render(): View|Factory
{
return view(
'livewire.product-list',
[
'products' => Product::select('id', 'title', 'price', 'thumbnail', 'slug')
->where('category_id', $this->categoryId)
->orderBy($this->sortColumn, $this->sortOrder)
->paginate(15),
]
);
}
public function updateSort()
{
switch ($this->sort) {
case 'title_asc':
$this->sortColumn = 'title';
$this->sortOrder = 'asc';
break;
case 'title_desc':
$this->sortColumn = 'title';
$this->sortOrder = 'desc';
break;
case 'price_asc':
$this->sortColumn = 'price';
$this->sortOrder = 'asc';
break;
case 'price_desc':
$this->sortColumn = 'price';
$this->sortOrder = 'desc';
break;
default:
$this->sortColumn = 'created_at';
$this->sortOrder = 'desc';
}
}
}
This is my view product-list.blade.php
:
@pushOnce('styles')
@vite(['resources/scss/components/product-list.scss'])
@endPushOnce
<div>
<div class="flex flex-col space-y-8 md:space-y-0 md:flex-row justify-between">
<select name="categories" class='product-filter' wire:change="updateSort" wire:model="categoryId">
@foreach($this->categories as $category)
<option class="category-item" value="{{ $category->id }}" wire:key={{ $category->id }}>{{ $category->name }}</option>
@endforeach
</select>
<select name="sort" class='product-filter' wire:change="updateSort" wire:model="sort">
@foreach(['latest' => 'latest', 'title_asc' => 'title (a->z)', 'title_desc' => 'title (z->a)', 'price_asc' => 'price: low to high', 'price_desc' => 'price: high to low'] as $sortKey=>$sortItem)
<option class="sort-item" value="{{ $sortKey }}" wire:key={{ $sortKey }}>{{ $sortItem }}</option>
@endforeach
</select>
</div>
<div class="product-container animate__animated">
@foreach ($products as $productIndex => $product)
<div class="product-card" wire:key={{ $product->id }}>
<p class="product-title">{{ $product->title }}</p>
<p class="product-price">$ {{ $product->price }} USD</p>
<img class="product-image" src="{{ $product->thumbnail }}" alt="Product Thumbnail" />
<x-button content="View details" type="secondary" link="{{ 'products/' . $product->slug }}"></x-button>
</div>
@endforeach
</div>
<div class="pagination">
{{ $products->links() }}
</div>
</div>
Hope this help.
Upvotes: 0
Reputation: 2763
For my reference and who come across this issue.
Some times you need to add your query
with the pagination
in the mount
or maybe in some function
with an id
.
Here is the two options you have to do so.
As the error says
Livewire component's public property must be of type: [numeric, .... Only protected or private properties can be set
So in this case you can define the property as protected
instead of public
and call it in the render.
Quick example.
protected $posts;
public function mount()
{
$this->posts = Post::paginate(15);
}
public function render()
{
return view('livewire.view',[
'posts'=>$this->posts
]);
}
More elegant why is to use the computed properties
Quick example.
use WithPagination;
public $postId;
public function getPostsProperty(): LengthAwarePaginator
{
return Post::find($this->postId)->comments()->paginate(10);
}
public function render(): Factory|View|Application
{
return view('livewire.view');
}
Now in this example $this->posts
can be accessed from either the component's class or Blade view. Read more from here
And more reference from GitHub
issue here
Upvotes: 2