Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

MichielE's avatar

Laravel Liveware table changes values on refresh

I'm not sure wether to post this in Laravel or Livewire, but I thought this was the best place. So I started with Livewire and created a simple datatable. Live search, pagination and sortable all works. In my table I show several data. But whenever I filter (either by search, pagination or sortable) the component changes the value of the status of all the posts to "Published". When I hit refresh everything works fine. Can anyone think of something what it could be? It is only the status that gets changed. All the other values remains untouched.

The status come from an Enum. But even when I remove the enum or cast from the model, the behaviour remains the same.

PostIndex.php (Livewire)

<?php

namespace App\Livewire\Admin;

use Livewire\Component;
use Livewire\WithPagination;

use App\Models\Post;
class PostIndex extends Component
{
    use WithPagination;

    public int $perPage = 10;
    public string $search = '';
    public string $sortField = 'title';
    public string $sortDirection = 'asc';

    protected array $queryString = ['sortField', 'sortDirection'];

    public function sortBy($fieldName) {
        $this->sortDirection = $this->sortField === $fieldName
            ? $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'
            : $this->sortDirection = 'asc';

        $this->sortField = $fieldName;
    }

    public function render()
    {
        return view('livewire.admin.post-index', [
            'tableContents' => Post::where('title', 'like', '%' . $this->search . '%')
                                     ->orderBy($this->sortField, $this->sortDirection)
                                     ->paginate($this->perPage),
        ]);
    }
}

Post-Index (View)

<div>

    <div class="row">

        <div class="col-4">

            <x-form.input
                wire:model.live="search"
                placeholder="{{ __('Search Results ..') }}"/>

        </div>

        <div class="col-8 flex action-group justify-content-end">

            <x-form.group>
                <x-form.select wire:model.live="perPage">
                    <option value="10">10</option>
                    <option value="25">25</option>
                    <option value="50">50</option>
                </x-form.select>
            </x-form.group>

            <x-button.add :permission="'post'" :route="route('post.create')">{{ __('Article') }}</x-button.add>

        </div>

    </div>

    <div class="row">

        <div class="align-middle min-w-full overflow-x-auto overflow-hidden sm:rounded-lg">

            <x-table>

                <x-slot name="header">

                    <x-table.heading sortable wire:click="sortBy('title')" :direction="$sortField === 'title' ? $sortDirection : null" class="w-full">{{ __('Title') }}</x-table.heading>
                    <x-table.heading>{{ __('Category') }}</x-table.heading>
                    <x-table.heading sortable wire:click="sortBy('status')" :direction="$sortField === 'status' ? $sortDirection : null">{{ __('Publicatie') }}</x-table.heading>
                    <x-table.heading>{{ __('Status') }}</x-table.heading>
                    <x-table.heading>{{__('Actions')}}</x-table.heading>

                </x-slot>

                <x-slot name="body">

                    @forelse($tableContents as $tableContent)
                        <x-table.row>
                            <x-table.cell>{{ \Str::words($tableContent->title, 5, ' ..') }}</x-table.cell>
                            <x-table.cell>{{ $tableContent->category->name }}</x-table.cell>
                            <x-table.cell>
                                @if($tableContent->status === 'published')
                                    <span class="badge bg-success d-block">{{ __(ucfirst($tableContent->status)) }}</span>
                                @elseif($tableContent->status === 'unpublished')
                                    <span class="badge bg-danger d-block">{{ __(ucfirst($tableContent->status)) }}</span>
                                @elseif($tableContent->status === 'archived')
                                    <span class="badge bg-secondary d-block">{{ __(ucfirst($tableContent->status)) }}</span>
                                @else
                                    <span class="badge bg-info d-block">{{ __(ucfirst($tableContent->status)) }}</span>
                                @endif
                            </x-table.cell>
                            <x-table.cell>
                                @if($tableContent->status === 'published')
                                    @if($tableContent->published === null)
                                        <span class="badge bg-success d-block">{{ __('Online') }}</span>
                                    @elseif($tableContent->unpublished_at > now())
                                        <span class="badge bg-success d-block">{{ __('Online') }}</span>
                                    @else

                                    @endif
                                @else
                                    <span class="badge bg-danger d-block">{{ __('Offline') }}</span>
                                @endif
                            </x-table.cell>
                            <x-table.cell class="flex index-button">
                                <x-button.edit :permission="'post'" :id="$tableContent->id">{{ __('Edit') }}</x-button.edit>
                                <x-button.delete :permission="'post'" :id="$tableContent->id">{{ __('Delete') }}</x-button.delete>
                            </x-table.cell>

                        </x-table.row>
                    @empty
                        <x-table.row>

                            <x-table.cell colspan="5">
                                <div class="flex justify-center items-center">
                                    <span>{{ __('No Results Found') }}</span>
                                </div>
                            </x-table.cell>

                        </x-table.row>
                    @endforelse

                </x-slot>

            </x-table>

            <div class="mt-2">
                {{ $tableContents->links() }}
            </div>

        </div>

    </div>

</div>

Post.php (Model)

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

use App\Enums\PublishedState;

use Spatie\MediaLibrary\MediaCollections\File;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\Permission\Traits\HasRoles;

use App\Scopes\PublishedScope;

class Post extends Model implements HasMedia
{
    use HasFactory;
    use HasRoles;
    use InteractsWithMedia;

    protected static function booted()
    {
        return static::addGlobalScope(new PublishedScope);
    }

    protected $fillable = [
        'user_id',
        'category_id',
        'slug',
        'title',
        'caption',
        'content',
        'meta_title',
        'meta_description',
        'meta_tags',
        'og_title',
        'og_description',
        'og_slug',
        'og_type',
        'og_locale',
        'status',
        'published_at',
        'unpublished_at',
    ];

    protected $casts = [
        'user_id' => 'integer',
        'category_id' => 'integer',
        'state' => PublishedState::class,
        'published_at' => 'datetime',
        'unpublished_at' => 'datetime',
    ];

    protected $dates = [
        'published_at',
        'unpublished_at',
        'created_at',
        'updated_at',
    ];

    public function user()
    {
        return $this->belongsTo(\App\Models\User::class);
    }

    public function category()
    {
        return $this->belongsTo(\App\Models\Category::class);
    }
}

PublishedState.php

<?php
namespace App\Enums;

enum PublishedState: string
{
    case Archived = 'archived';
    case Draft = 'draft';
    case Published = 'published';
    case Unpublished = 'unpublished';
}
0 likes
2 replies
kiwi0134's avatar

Please make sure to add the key attribute on your <x-table.row> tag. It's required in loops so Livewire / Alpine can uniquely identify every loop item inside your DOM when updating it with fresh data. It doesn't really matter what the key is, as long as it's unique on your page. It is visible to your end users inside the page source, so don't use anything you don't want them to see.

Please refer to the corresponding Livewire documentation for more details about that: https://livewire.laravel.com/docs/components#adding-wirekey-to-foreach-loops

MichielE's avatar

@kiwi0134 Unfortunately that is not the case. I tried that with no luck.

The weird thing is that it only happens on the data with $tableContent->status. The other values (Title and Category) doesn't change and seem to stay the same with the correct data.

Please or to participate in this conversation.