Home > Software engineering >  Meilisearch Laravel Livewire component - unchecked checkboxes disappearing when using wire:model
Meilisearch Laravel Livewire component - unchecked checkboxes disappearing when using wire:model

Time:01-03

I'm building a full page Livewire component that filters Meilisearch results.

When I check one of the filters the remaning checkboxes disappear, and I am struggling to understand why. I followed a tutorial to figure out how to build the bulk of the component and in the tutorial the behaviour didn't occur. I've deconstructed the component to the bare minimum and the behaviour happens as soon as I wire up the checkboxes.

Here is the component:

 <?php

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\CourseDate;
use App\Models\Course;

class CourseBrowser extends Component
{

    public $queryFilters = [];

    public $priceRange = [
        'min' => null,
        'max' => null
    ];

    public function mount()
    {
        $this->queryFilters = [
            'venue' => [],
            'type' => [],
            'category' => [],
            'days'  => [],
            'supplier' => []
        ];
    }

    public function render()
    {

        $search = CourseDate::search(
            '',
            function ($meilisearch, string $query, array $options) {

                $filters = collect($this->queryFilters)
                    ->filter(fn ($filter) => !empty($filter))
                    ->recursive()
                    ->map(function ($value, $key) {
                        return $value->map(fn ($value) => $key . ' = "' . $value . '"');
                    })
                    ->flatten()
                    ->join(' AND ');

                $options['facets'] = ['venue', 'category', 'type', 'supplier', 'days'];
                $options['filter'] = null;

                if ($filters) {
                    $options['filter'] = $filters;
                }

                if ($this->priceRange['max']) {
                    $options['filter'] .= (isset($options['filter']) ? ' AND ' : '') . 'price <= ' . $this->priceRange['max'];
                }

                return $meilisearch->search($query, $options);
            }
        )->raw();

        $coursedates = CourseDate::find(collect($search['hits'])->pluck('id'));

        $minPrice = Course::all()->min('price');
        $maxPrice = Course::all()->max('price');

        $this->priceRange['min'] = $this->priceRange['min'] ?: $minPrice;
        $this->priceRange['max'] = $this->priceRange['max'] ?: $maxPrice;

        return view('livewire.course-browser')
            ->with(
                [
                    'coursedates' => $coursedates,
                    'filters' => $search['facetDistribution'],
                    'minPrice' => $minPrice,
                    'maxPrice' => $maxPrice,
                ]
            );
    }
}

Here is the Blade view. I had originally posted an edited section showing just the checkboxes but I have edited this question to show the view in its entirety.


<div>

    <div >
        <a href="{{ route('home') }}">Home</a> / <a href="{{ route('course.index') }}">Courses</a>
    </div>

    <div >
        <div >
            All Courses
        </div>
    </div>
    <div >
        <div >
            <div >
                <h6 >Filters</h6>
                <hr >
                <h6 >Price </h6>
                <div >
                    <input type="range"
                           min="{{ $minPrice }}"
                           max="{{ $maxPrice }}"
                           step="25"
                           
                           wire:model="priceRange.max" />
                    <span >
                        (£{{ $priceRange['max'] }})
                    </span>
                </div>
                @foreach ($filters as $title => $filter)
                    <h6 >{{ Str::title($title) }}</h6>
                    @foreach ($filter as $option => $count)
                        <div wire:ignore.self
                             >
                            <input type="checkbox"
                                   
                                   wire:model="queryFilters.{{ $title }}"
                                   id="{{ $title }}_{{ strtolower($option) }}"
                                   value="{{ $option }}">
                            <label 
                                   for="{{ $title }}_{{ strtolower($option) }}">{{ $option }} ({{ $count }})</label>
                        </div>
                    @endforeach
                @endforeach
            </div>
        </div>

        <div >
            <h6 >{{ $coursedates->count() }} {{ Str::plural('course', $coursedates) }} matching your filters</h6>
            @forelse ($coursedates as $coursedate)
                <div wire:loading.
                     >
                    <span >{{ $coursedate->course->category->name }}</span>
                    <span >{{ $coursedate->course->supplier->name }}</span>
                    <a href="{{ route('course.show', $coursedate->course->slug) }}">
                        <img src="{{ asset('storage/courses/images/' . $coursedate->course->title_image) }}"
                             alt="{{ $coursedate->course->slug }}"
                             >
                    </a>

                    <div >
                        <h6 ><a href="{{ route('course.show', $coursedate->course->slug) }}">{{ $coursedate->course->name }} </a></h6>
                        <p >{{ $coursedate->course->tagline }}</p>
                        {{-- Pills Container --}}
                        <div >
                            <div >
                                <div >
                                    <svg xmlns="http://www.w3.org/2000/svg"
                                         viewBox="0 0 24 24"
                                         fill="currentColor"
                                         >
                                        <path d="M4.5 3.75a3 3 0 00-3 3v.75h21v-.75a3 3 0 00-3-3h-15z" />
                                        <path fill-rule="evenodd"
                                              d="M22.5 9.75h-21v7.5a3 3 0 003 3h15a3 3 0 003-3v-7.5zm-18 3.75a.75.75 0 01.75-.75h6a.75.75 0 010 1.5h-6a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5h3a.75.75 0 000-1.5h-3z"
                                              clip-rule="evenodd" />
                                    </svg>
                                </div>
                                <div >
                                    £{{ $coursedate->course->price }}
                                </div>
                            </div>

                            <div >
                                <div >
                                    <svg xmlns="http://www.w3.org/2000/svg"
                                         viewBox="0 0 24 24"
                                         fill="currentColor"
                                         >
                                        <path fill-rule="evenodd"
                                              d="M11.54 22.351l.07.04.028.016a.76.76 0 00.723 0l.028-.015.071-.041a16.975 16.975 0 001.144-.742 19.58 19.58 0 002.683-2.282c1.944-1.99 3.963-4.98 3.963-8.827a8.25 8.25 0 00-16.5 0c0 3.846 2.02 6.837 3.963 8.827a19.58 19.58 0 002.682 2.282 16.975 16.975 0 001.145.742zM12 13.5a3 3 0 100-6 3 3 0 000 6z"
                                              clip-rule="evenodd" />
                                    </svg>
                                </div>
                                <div >
                                    {{ $coursedate->venue->city }}
                                </div>
                            </div>
                            @foreach ($coursedate->actualdates as $dates)
                                @if ($coursedate->course->number_of_days > 1)
                                    <div >
                                        <div >
                                            <svg xmlns="http://www.w3.org/2000/svg"
                                                 viewBox="0 0 24 24"
                                                 fill="currentColor"
                                                 >
                                                <path d="M12.75 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM7.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM8.25 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.75 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM10.5 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM12.75 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM14.25 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 13.5a.75.75 0 100-1.5.75.75 0 000 1.5z" />
                                                <path fill-rule="evenodd"
                                                      d="M6.75 2.25A.75.75 0 017.5 3v1.5h9V3A.75.75 0 0118 3v1.5h.75a3 3 0 013 3v11.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V7.5a3 3 0 013-3H6V3a.75.75 0 01.75-.75zm13.5 9a1.5 1.5 0 00-1.5-1.5H5.25a1.5 1.5 0 00-1.5 1.5v7.5a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5v-7.5z"
                                                      clip-rule="evenodd" />
                                            </svg>
                                            {{ $dates->label }}
                                        </div>
                                        <div >
                                            {{ $dates->date->format('jS M Y') }}
                                        </div>
                                    </div>
                                @else
                                    <div >
                                        <div >
                                            <svg xmlns="http://www.w3.org/2000/svg"
                                                 viewBox="0 0 24 24"
                                                 fill="currentColor"
                                                 >
                                                <path d="M12.75 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM7.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM8.25 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM9.75 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM10.5 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM12.75 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM14.25 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 17.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 15.75a.75.75 0 100-1.5.75.75 0 000 1.5zM15 12.75a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM16.5 13.5a.75.75 0 100-1.5.75.75 0 000 1.5z" />
                                                <path fill-rule="evenodd"
                                                      d="M6.75 2.25A.75.75 0 017.5 3v1.5h9V3A.75.75 0 0118 3v1.5h.75a3 3 0 013 3v11.25a3 3 0 01-3 3H5.25a3 3 0 01-3-3V7.5a3 3 0 013-3H6V3a.75.75 0 01.75-.75zm13.5 9a1.5 1.5 0 00-1.5-1.5H5.25a1.5 1.5 0 00-1.5 1.5v7.5a1.5 1.5 0 001.5 1.5h13.5a1.5 1.5 0 001.5-1.5v-7.5z"
                                                      clip-rule="evenodd" />
                                            </svg>
                                        </div>
                                        <div > {{ $dates->date->format('jS M Y') }} </div>
                                    </div>
                                @endif
                            @endforeach
                        </div>
                        <div >
                        </div>
                    </div>
                </div>
            @empty
                No Course Dates to show
            @endforelse
        </div>
    </div>
</div>


The component in itself works fine. But as you can see in the gif below, the non selected checkboxes disappear, so I cannot apply multiple filters.

Checkboxes that arent selected disappear

I suspect the problem lies in the way I'm storing the filters in the array but I can't see the woods for the trees at the moment. The filtering itself currently works, and any attempt to change the format of the array results in it not working at all.

Thanks in advance.

CodePudding user response:

There are certain bugs in Livewire, and this is also one of them.

Try enclosing the each checkbox in a div.

 <div >
 <input type="checkbox" wire:model="queryFilters.{{ $title }}"
                             id="{{ $title }}_{{ strtolower($option) }}"
                             value="{{ $option }}">
</div>

If the issue still presist, add wire:ignore.self to container

<div  wire:ignore.self>
......

</div>

CodePudding user response:

I've dumped the contents of the array as it's being filled up on the page

The array contents using var_export

Each time you check a box it adds the value into the array. Obviously In the case of the Venue group of checkboxes I can't add a second value because the unselected ones disappear. But what I'd expect is an array that looks like this for example:


 $this->queryFilters = [
            'venue' => ['Norwich', 'Peterborough'],
            'type' => ['In Person'],
            'category' => ['Hair'],
            'days'  => [],
            'supplier' => []
        ];

I'd then map through the values and build the filter string for Mielesearch, which happens in the closure futher down.

"venue = "Norwich" AND venue = "Peterborough" AND type = "In Person" AND category = "Hair"" 

CodePudding user response:

After some perusal of the Meilisearch docs:

facetDistribution contains an object for every given facet. For each of these facets, there is another object containing all the different values and the count of matching documents. Note that zero values will not be returned: if there are no romance movies matching the query, romance is not displayed.

That suggests that the behaviour is normal

  • Related