Hadi Hassan
Hadi Hassan

Reputation: 183

Livewire throwing error when using paginated data

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

Answers (3)

Hadi Hassan
Hadi Hassan

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

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

Yousef Altaf
Yousef Altaf

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.

  1. In the mount

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
        ]);
    }
  1. In some function.

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

Related Questions