I have the following livewire code to perform a search of page titles which works fine on the first search, but subsequent searches do not completely remove the results from the previous search.
namespace App\Http\Livewire\Page;
use App\Models\Page;
use Livewire\Component;
class Search extends Component
{
public $searchQuery = "";
public $searchResults;
public function resetSearchValue() {
$this->searchQuery = "";
}
public function mount() {
$this->reset();
}
public function render()
{
if(strlen($this->searchQuery) > 2) {
$this->searchResults = Page::query()
->select(['title', 'slug'])
->where('title', 'like', "%{$this->searchQuery}%")
->get();
}
return view('livewire.page.search');
}
}
and
<div>
<input
wire:model.debounce.350ms="searchQuery"
@focus="searchFocused = true"
@blur="searchFocused = false"
@click.outside="$wire.resetSearchValue()"
autocomplete="off"
type="text"
placeholder="Search..."
id="sitesearch"
>
@if(strlen($searchQuery) > 0)
<div
role="menu"
aria-orientation="vertical"
-labelledby="menu-button"
tabindex="-1"
>
<div role="none">
@if(strlen($searchQuery) > 2)
@if($searchResults->count() > 0)
@foreach($searchResults as $result)
<a
href="{{ route('pages.index', $result->slug) }}"
role="menuitem"
tabindex="-1"
id="menu-item-0"
>{{ $result->title }}</a>
@endforeach
@else
<p>
No results found
</p>
@endif
@else
<p>
You need to type at least 3 characters
</p>
</div>
</div>
@endif
</div>
Searching for "Page" for example will return something like
- My Page 1
- Page 2
- Wonderful Page 3
But if I then search for "Page Z" the results I get are
- My Page 1
- Page 2
- No results found
If I the search for "Page Zz" the results I get are
- My Page 1
- No results found
I can't work out why it's not clearing the results from previous results.
CodePudding user response:
Basically, this is the result of a DOM-diffing issue Livewire has when it can't keep track of elements, typically dynamic elements generated in a loop.
The simple solution to this, is to add wire:key
with a value to the root element in your loop, like shown below.
@if(strlen($searchQuery) > 2)
@if($searchResults->count() > 0)
@foreach($searchResults as $result)
<a
href="{{ route('pages.index', $result->slug) }}"
role="menuitem"
tabindex="-1"
id="menu-item-0"
wire:key="result-{{ $result->id }}"
>{{ $result->title }}</a>
@endforeach
@else
<p wire:key="no-results">
No results found
</p>
@endif
@else
<p wire:key="searchquery-short">
You need to type at least 3 characters
</p>
@endif
Also, I've added these to the other options which may be shown in place, just so it's no doubt about which element it should show.
Just a note, all the values to wire:key
on a page must be unique (like with ID attributes to HTML elements).