How to avoid special treatment of empty filters/searches?

I often run into the problem in Livewire to need a special treatment when a filter or search is empty. I copy a lot of code because I could not find a way to say: If empty, just return everything.

Here’s a minimal example:

Not nice:

public $status = ''; // default, can also be 'yes' or 'no'

if (! $this->status) 
{
  return view('livewire.users', [
     'users' => User::get(),
  ]);
}

return view('livewire.users', [
  'users' => User::where('status', $this->status)->get(),
]);

Would be nicer:

public $status = '*'; // default, can also be 'yes' or 'no'

return view('livewire.users', [
     'users' => User::where('status', $this->status)->get(),
  ]);

This would translate to User::where('status', *) (any status, so return everything) in my dream implementation. This way I would not need a special case for the empty filter/search. I could not find a way in Eloquent to only use the where if there is a value and just ignore it or return all records if the second parameter in where is empty or a special character (just like *).

Is there any way to say: If the where column is empty, just don’t filter but return everything?

The easeiest way is to let mysql do it for you.
In your User model:

public function scopeSearchStatus($query, $search)
{
    return $query->where('status', 'LIKE', "%$search%");
} 

Component Use:

'users' => User::searchStatus($this->status)->get(),

An empty search string will return all records.

This is a good design pattern for live searching. Just keep in mind that if searching isn’t the only responsibility of the component, running a query on the render method means every request is going to result in a query and they can stack up quickly.

1 Like

Thanks, @xxdalexx that helped!

Hey @minthemiddle, @xxdalexx,

Another possible solution is using the when() conditional clause from query builder.
For more info.

public $status = '';

$users = App\User::query(
  ->when($this->status, function ($query) {
    return $query->where('status', $this->status);
  })
  ->get();

I usually relegate as much as I can from eloquent to mysql because in most cases mysql can process logic quicker. I never considered using when() and this got me thinking I might be wrong here, because the when() should block mysql from having to do some work.

So I wrote a test that runs each the like and when approach 10k times each, and did it four times. In this case, using the when statement in php to block the query runs about 17% faster.

Good catch @leMaur, @minthemiddle I would change to the when approach.

However, there are a couple of typos in your example. query() is missing a closing bracket (and unneeded) and you need a use statement for the status. It should be:

$users = App\User::when($this->status, function ($query) use ($this->status) {
    return $query->where('status', $this->status);
})->get();
1 Like

Hey @xxdalexx,

I’m glad you found it useful.

Yeah your right, I did some typos because I wrote it down directly in the markdown. This is the problem with the IDE, the autocomplete made our life more easier. :yum:
Sorry for that!

I know query() is an optional but I use it a lot when I have more methods appended (stacked) to the model. It make my code more readable and more pleasant to my eyes.

Anyway @minthemiddle, to make your component more elegant and efficient, you can use the Livewire Computed Properties.

Instead of:

Use App\User;
Use Livewire\Component;

// I guess the component name :)
class Users extends Component
{
  public $status = '';

  public function render()
  {
    return view('livewire.users', [
      'users' => User::when($this->status, function ($query) {
        return $query->where('status', $this->status);
      })->get(),
    ]);
  }
}

You can do this with a computed property:

Use App\User;
Use Livewire\Component;

class Users extends Component
{
  public $status = '';

  // Computed Property
  public function getUsersProperty()
  {
    return User::when($this->status, function ($query) {
      return $query->where('status', $this->status);
    })->get();

    // One step further, I prefer to return only the data 
    // I really need for my component.
    // To do so, you can use the `select()` method.
    //
    // Here, I use `query()` method to stack all methods
    // because I found more readable (as I explained before).
    //
    // return User::query()
    //   ->select('name', 'email', 'status')
    //   ->when($this->status, function ($query) {
    //     return $query->where('status', $this->status);
    //   })
    //   ->get();
  }

  public function render()
  {
    return view('livewire.users');
  }
}

Pay attention that you need to update your view from $users to $this->users.

The benefit of this approach is explained in the end of the Livewire’s documentation .

Computed properties are cached for an individual Livewire request lifecycle. Meaning, if you call $this->users 5 times in a component’s blade view, it won’t make a seperate database query every time.


I hope you find this useful.

Cheers,
Maurizio

P.S.
If you need more information about Eloquent optimization, Jonathan Reinink wrote a bunch of articles about this topic. You can find them in his website under Articles.
He’s also going to release a course Eloquent Performance Patterns.