Why does Livewire run a new query on each render and why are relationships lost

Viewed 613

Scenario

What i try to do

I am creating a multicolumn user index page, where the right column shows details from the user selected in the left column. When selected, the user is not pulled out of the collection but freshly out of the database, so the data is up to date.

I defer the loading of the user list using the described method in the livewire documentation.

The user has a 'roles' relationship, which is displayed in the list column.

What I'd expect

I would expect that once the $this→users is set as a collection of the users and a user is selected, only the query will fire for getting the data for this user.

What actually happens

When a user is selected, a query for getting all users from the database is run (again), and because of the fact that the roles from the user are displayed in the list view, for each user, a new query is executed. After that, a query for getting the selected user is executed. Afterwards another query for getting the roles of the user is fired to.

So my questions

  1. Why does Livewire lose the relations that were eager loaded in the first declaration of public $users?
  2. Why is it that Livewire reruns the query for getting all users, while the public $users is already defined as a collection of users?

Files:

  • UserListDetail.php
    <?php

    namespace App\Http\Livewire;

    use App\Models\User;
    use Livewire\Component;

    class UsersListDetail extends Component {

        public string $search   = '';
        public        $users;
        public        $selectedUser;
        public int    $timesRun = 0;

        public bool $readyToLoadUserList = false;

        protected $queryString = [
            'search' => [ 'except' => '' ],
        ];

        // Defer loading users
        public function readyToLoadUserList()
        {
            // Get all users with roles relationship
            $this->users = User::with('roles')->get();

            $this->readyToLoadUserList = true;
        }

        public function selectUser(int $userId)
        {
            $this->selectedUser = User::with('roles')->find($userId);
        }

        public function render()
        {
            return view('livewire.users-list-detail', [
                    'selectedUser' => $this->selectedUser,
                ]
            );
        }
    }
  • simplified version of user-list-detail.blade.php
<div>
    <div wire:init="readyToLoadUserList">
        @if($readyToLoadUserList)
            <ul>
                @foreach($users as $user)
                    <li wire:click="selectUser({{ $user->id }})">
                        {{ $user→name_first }} {{ $user→name_last }},
                        @foreach($user→roles as $role)
                            {{ $role→label }},
                        @endforeach
                    </li>
                @endforeach
            </ul>
        @endif
    </div>

    <div>
        @isset($selectedUser)
            {{ $name_first
        @endisset
    </div>
</div>
1 Answers

When selectUser() method is triggered, the livewire will re-render the blade and since wire:init="readyToLoadUserList" is there, it will load every user (again).

Replce readyToLoadUserList() with mount() and simply keep wire:init="" empty.

Also, condition with @if($users->count() > 0)

Related