livewire: QueryString doesn't appear to support nested values.

Description

You can do;

public $name = 'test';
protected $queryString = [
    'name'
];

You can’t do;

public $filters = [
    'name' => 'test'
];
protected $queryString = [
    'filters.name'
];
// or
protected $queryString = [
    'filters' => [
        'name'
    ]
];

You can provide filters asa whole as a query string, but it ends up being super messy, especially in a use-case such as filters for something.

Context

  • Livewire version: 2.3.0
  • Laravel version: 8.11.2
  • Browser: Chrome

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 19

Most upvoted comments

+1 for this.

Hi everyone! I figured it out.

// YourComponent or trait

public array $filters = [
    'id' => '',
    'name' => '',
    'isVerified' => 'yes',
];

public $queryString = [
    'filterId' => ['except' => '', 'as' => 'id'],
    'filterName' => ['except' => '', 'as' => 'name'],
    'filterIsVerified' => ['except' => 'yes', 'as' => 'verified'],
];

public function __get($property)
{
    if (str_starts_with($property, 'filter')) {
        $key = Str::camel(Str::after($property, 'filter'));

        if (! array_key_exists($key, $this->filters)) {
            throw new Exception(sprintf(
                'Filter [%s] not found in filters: [%s]',
                $key,
                collect($this->filters)->keys()->join(', '),
            ));
        }

        return $this->filters[$key];
    }

    return parent::__get($property);
}

The URI would be like this:

...?id=18&name=John&verified=no

Of course the filter prefix is optional, and you can omit the if-statement in __get()


{{-- Component's Blade --}}

<input type="number" min="0" wire:model="filters.id" />
<input type="text" wire:model="filters.name" />
<select wire:model="filters.isVerified">
    <option value="yes">Yes</option>
    <option value="no">No</option>
</select>

@calebporzio can you please add this feature? Thanks in advance!

@sathio, oh, I beg my pardon. I took a closer look at my enhanced version of this code and I saw that I initialize these filters values on boot.

Basically, you should do something like this:

public function boot(): void
{
    $this->filters = [
        'txt' => request()->query('txt'),
    ];
}

Automate this thing via $this->filters = map($this->filters) blah blah blah…

Remember that boot() method in traits is written differently, so in your case it should be bootWithFilters(): See here for more information.

@sathio No, I’ve just checked it out and it works perfectly fine. After a reload it takes values from query string and then passes them to inputs via wire:model. wire:model works because of my __get() magic method. Thus, that’s a working solution

@PostScripton looks like your method will not fill the form elements upon reload, am I wrong?

@sathio Dunno. Can’t remember whether I’ve tested it or not. Though couldn’t find any troubles with this solution on my production server since then. Try it our and let me know please 😄 Maybe I should fix it on my prod 😂

yeah i think you should have a look at it ))

REOPEN 😃

Agree this is not a minor shortcoming. Without dot notation, query strings (and array props) lose so much of their potential. The viable strategies are: either don’t use queryString at all (bad), or don’t use array props for filters (better but sad). The second option clearly denies smart approaches like the one suggested in the Surge demo:

public function updatedFilters()
{
    $this->resetPage();
}