skip to Main Content

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…

Before searching:
Before searching

After searching:
After searching

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


  1. Try using wire:model.debounce on Search blade:

    <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.debounce.500ms ="searchTerm"
    >
    

    Then:

    class Search extends Component
    {
        public string $searchTerm = "";
    
        public function render(): View
        {
            return view('livewire.search');
        }
    
        public function updatedSearchTerm(): void
        {
            $this->emit("searchArmors", $this->searchTerm);
        }
    }
    

    Reference: https://laravel-livewire.com/docs/2.x/lifecycle-hooks

    Login or Signup to reply.
  2. 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

    @foreach($armorSet->armors as $armor)
        <livewire:armor-card :armor="$armor" wire:key="armor_card_{{ $armor->id }}" />
    @endforeach
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search