I’m attempting to link two Livewire components so that a change to one (the Search box) will update the other (the page showing either the search results or, if no search input, the default of all items). I know I can use events to do this, and I even have other Livewire components that use event emitters and listeners. However, this particular one is not behaving properly and I need some guidance on what I’m doing wrong. Here are two screenshots showing what the page should look like both before and after searching. It acts more like a “filter,” but still…
The problem I’m having is that it sometimes does the search correctly on the first time after a refresh, but not after more keystrokes or characters are added to the Search box. When the “X” to the right is clicked, nothing changes except the box being emptied. Searching for one character seems to work, even if you backspace it to clear the search box, resulting in the view showing all armors categorized correctly. But entering two or more characters seems to break it. In addition, without the .debounce
, the ->where()
clause of the Eloquent query doesn’t even seem to work at all!
I have a “Main” component that consists of the main view where the armor cards appear:
class Main extends Component
{
public Collection $armorSets;
public Collection $filteredArmors;
protected $listeners = ["searchArmors"];
public function mount(): void
{
$this->armorSets = ArmorSet::with([
"armors.resources" => function ($query) {
$query->orderBy("tier", "asc");
},
])->get();
$this->filteredArmors = collect();
}
public function render(): View
{
return view("livewire.main");
}
public function searchArmors(string $searchTerm): void
{
if ($searchTerm) {
$this->filteredArmors = Armor::where("name", "like", "%$searchTerm%")
->with([
"resources" => function ($query) {
$query->orderBy("tier", "asc");
},
])
->where("upgradable", true)
->get();
} else {
$this->filteredArmors = collect();
}
}
}
<div class="py-12">
@if($filteredArmors->count() > 0)
<ul role="list" class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 mb-6">
@foreach($filteredArmors as $armor)
<livewire:armor-card :armor="$armor" />
@endforeach
</ul>
@else
@foreach($armorSets as $armorSet)
<div>
@if($armorSet->armors->contains(fn ($armor) => $armor->upgradable))
<div class="border-b border-gray-200 pb-5 mb-5">
<h3 class="text-xl font-medium leading-6 text-gray-900">{{ $armorSet->name }}</h3>
</div>
<ul role="list" class="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 mb-6">
@foreach($armorSet->armors as $armor)
<livewire:armor-card :armor="$armor" />
@endforeach
</ul>
@endif
</div>
@endforeach
@endif
</div>
And I have the Search component:
class Search extends Component
{
public string $searchTerm = "";
public function render(): View
{
return view('livewire.search');
}
public function onChange(): void
{
$this->emit("searchArmors", $this->searchTerm);
}
}
<div class="flex flex-1">
<form class="flex w-full md:ml-0" action="#" method="GET">
<label for="search-field" class="sr-only">Search</label>
<div class="relative w-full text-gray-400 focus-within:text-gray-600">
<input
id="search-field"
class="block h-full w-full border-transparent py-2 pl-8 pr-3 text-gray-900 placeholder-gray-500 focus:border-transparent focus:placeholder-gray-400 focus:outline-none focus:ring-0 sm:text-sm"
placeholder="Search"
type="search"
name="search"
wire:model="searchTerm"
wire:keydown.debounce="onChange"
>
</div>
</form>
</div>
I’ve simplified the code above to remove some unnecessary parts.
What am I doing wrong in Livewire here?
2
Answers
Try using wire:model.debounce on Search blade:
Then:
Reference: https://laravel-livewire.com/docs/2.x/lifecycle-hooks
Have you tried keying your looped components? This may help Livewire to better keep track of things.
https://laravel-livewire.com/docs/2.x/troubleshooting#dom-diffing-cures