Building a Reusable Filter Component in Laravel Livewire: A Step-by-Step Guide

featured_img1737365924.webp

Building a Reusable Filter Component in Laravel Livewire: A Step-by-Step Guide

Table of Contents

    Livewire is an open-source library for Laravel, enhancing the development of dynamic and interactive user interfaces with minimal effort. Unlike traditional JavaScript-heavy solutions, Livewire allows developers to write interactive, reactive UI components using PHP, Blade, and Laravel's existing functionality. This library enables seamless server-client communication, allowing for real-time updates without requiring a page reload, providing a smooth and responsive user experience.

    By leveraging Livewire, developers can create components such as forms, charts, and dynamic content areas, without the need to write complex JavaScript. The library simplifies the development process by allowing you to define your components in pure PHP, reducing the complexity of handling client-side behavior. Livewire works by automatically sending updates from the server to the client, ensuring that the interface stays in sync with the application's state. This makes it an ideal choice for building modern, real-time applications while maintaining Laravel's elegant syntax and structure.

    Filtering is a crucial feature in many web applications, especially for managing data-heavy interfaces like admin dashboards or user portals. With Laravel Livewire, creating reusable components becomes seamless, allowing you to implement filters across multiple modules efficiently. In this guide, we will build a reusable common filter component using Laravel Livewire, enhancing code reusability and user experience.

    Preview:


    Step 1: Set Up Laravel with Livewire

    Before diving into the implementation, ensure you have a Laravel project set up with Livewire installed. I took an Example for PG : Payment Gateway to filter transactions.

    1. Open your terminal and run the following command to create a fresh Laravel application:
                                                  
                                                      composer create-project laravel/laravel PG
                                                  
                                              
    2. Next, install Livewire using this command:
                                                  
                                                      composer require livewire/livewire
                                                  
                                              

    Step 2: Create the Livewire Trait

    To avoid repetitive code, we will create a reusable Livewire trait that handles common filter logic.

    1. Generate a Livewire trait using:
                                              
                                                  php artisan livewire:make Admin\\Common\\FilterTrait
                                              
                                          
    2. Open FilterTrait.php and add the following code:
                                              
                                                      namespace App\Livewire\Admin\Common;
                                                      use Livewire\Component;
      
                                                      class FilterTrait extends Component
                                                      {
                                                          public $filter_search_columns = [
                                                              "pg_transactions" => [
                                                                  ["column" => "txn_ref_id","label"=>"PG Transaction ID"],
                                                                  ["column" => "order_id","label"=>"Order ID"],
                                                                  ["column" => "biller_id","label"=>"Biller ID"],            
                                                                  ["column" => "payment_id","label"=>"Payment ID"]            
                                                            ],                                                    
                                                          ];
      
                                                          public $filter_search_column = "";
                                                          public $filter_search_column_label = "";
                                                          public $filter_search = "";
                                                          public $filter_from_date = "";
                                                          public $filter_to_date = "";
                                                          public $filter_status = "";
                                                          public $filter_type = "";
                                                          public $filter_page_limit = 10;
                                                          public $page_limits = [10,20,30,40,50,100];
      
                                                          public function set_filter_search_column($column_id=0,$page){
                                                              $column_id = base64_decode($column_id);      
                                                              $column = $this->filter_search_columns[$page][$column_id];
                                                              $this->filter_search_column = $column['column'];
                                                              $this->filter_search_column_label = $column['label'];
                                                          }
      
                                                          public function filterRender(){    
                                                              $this->resetPage();
                                                          }
      
                                                          public function resetFilter($page=null)
                                                          {      
                                                              if($page){
                                                                  $this->set_filter_search_column(base64_encode(0),$page);
                                                              }
                                                              $this->filter_search = "";
                                                              $this->filter_from_date = "";
                                                              $this->filter_to_date = "";
                                                              $this->filter_status = "";  
                                                              $this->filter_type = "";  
                                                              $this->filter_page_limit = 10;    
                                                              $this->resetPage();        
                                                          }
      
                                                          // public function render()
                                                          // {
                                                          //     return view('livewire.admin.common.filter-trait');
                                                          // }
                                                      }
                                              
                                          

    This trait will handle common filter functionalities like column selection, search, date filtering, and pagination.

    Step 3: Create a Database and Model

    1. Generate a model and migration for transactions:
                                                  
                                                      php artisan make:model PG\\PgTransactionModel -m
                                                  
                                              
    2. Define Migration Schema Update the generated migration file with the following structure:

      Open database/migrations/xxxx_xx_xx_create_pg_transaction_models_table.php and add the following code:

                                                  
      
                                                      use Illuminate\Database\Migrations\Migration;
                                                      use Illuminate\Database\Schema\Blueprint;
                                                      use Illuminate\Support\Facades\Schema;
      
                                                      return new class extends Migration
                                                      {
                                                          /**
                                                          * Run the migrations.
                                                          */
                                                          public function up(): void
                                                          {
                                                              Schema::create('pg_transactions', function (Blueprint $table) {
                                                                  $table->id();                   
                                                                  $table->string('biller_id')->nullable();            
                                                                  $table->string('txn_ref_id')->nullable();
                                                                  $table->timestamp('txn_date')->nullable(); // Stores "txnDate"
                                                                  $table->string('txn_status')->nullable(); // Stores "txnStatus"
                                                                  $table->string('product_name')->nullable();
                                                                  $table->string('product_type')->nullable();
                                                                  $table->string('currency')->nullable();
                                                                  $table->decimal('amount', 15, 2)->default(0.00);
                                                                  $table->string('order_id')->index();
                                                                  $table->string('store_id')->nullable();
                                                                  $table->string('payment_mode')->nullable();
                                                                  $table->string('payment_method')->nullable();
                                                                  $table->string('payment_id')->nullable();           ;
                                                                  $table->string('ref_id_1')->nullable();
                                                                  $table->string('ref_id_2')->nullable();
                                                                  $table->string('ref_id_3')->nullable();          
                                                                  $table->timestamps();
                                                              });
                                                          }
      
      
                                                          /**
                                                          * Reverse the migrations.
                                                          */
                                                          public function down(): void
                                                          {
                                                              Schema::dropIfExists('pg_transactions');
                                                          }
                                                      };
      
                                                  
                                              
    3. Run the Migration Execute the migration to create the table: Ensure Your Database is Properly Configured.
                                                                                                  
                                                      php artisan migrate
                                                  
                                              
    4. Update the Model Open app\Models\PG\PgTransactionModel.php and define relationships and fillable fields
                                                                                                  
                                                      namespace App\Models\PG;
      
      
                                                      use Illuminate\Database\Eloquent\Factories\HasFactory;
                                                      use Illuminate\Database\Eloquent\Model;
                                                      use App\Models\Biller;
                                                      class PgTransactionModel extends Model
                                                      {
                                                          use HasFactory;
      
      
                                                          protected $table = "pg_transactions";
      
      
                                                          protected $fillable = [
                                                              'biller_id',      
                                                              'txn_ref_id',
                                                              'txn_date',
                                                              'txn_status',
                                                              'product_name',
                                                              'product_type',
                                                              'currency',
                                                              'amount',
                                                              'order_id',
                                                              'store_id',
                                                              'payment_mode',
                                                              'payment_method',
                                                              'payment_id',
                                                              'ref_id_1',
                                                              'ref_id_2',  
                                                              'ref_id_3',
                                                          ];
      
                                                          public function biller() {
                                                              return $this->hasOne(Biller::class, "biller_id", "biller_id");
                                                          }
      
                                                  
                                              

    Step 4: Build a Component with Filters

    1. Generate a Livewire Component Create a new Livewire component for the transaction list:
                                                  
                                                      php artisan make:livewire Admin\\PG\\Transactions\\index
                                                  
                                              
    2. Inherit and Use FilterTrait Open app\Livewire\Admin\PG\Transactions\Index.php and add the filter logic:
                                                  
                                                      use App\Livewire\Admin\Common\FilterTrait;
                                                          class Index extends FilterTrait
                                                          {
      
                                                          }
                                                  
                                              

      Full Code here.

                                                  
                                                      namespace App\Livewire\Admin\PG\Transactions;
                                                      use Livewire\Component;
                                                      use Livewire\Attributes\{Computed,On,Locked,Title};
                                                      use Illuminate\Support\Facades\Auth;
                                                      use Livewire\WithPagination;
                                                      use Livewire\WithoutUrlPagination;
                                                      use App\Livewire\Admin\Common\FilterTrait;
                                                      use App\Models\PG\PgTransactionModel;
                                                      class Index extends FilterTrait
                                                      {
                                                          use WithPagination, WithoutUrlPagination;
                                                          public $page = 'pg_transactions';
                                                          public $page_id = "";
                                                      
                                                      
                                                          public function mount(){
                                                              $this->authorizeAdminUser();
                                                              $this->set_filter_search_column(base64_encode(0),$this->page);
                                                          }
                                                      
                                                      
                                                          public function render()
                                                          {
                                                              $component_records =  $this->getTransactionData();
                                                              return view('livewire.admin.p-g.transactions.index',[
                                                                  'component_records' => $component_records
                                                              ])->extends('admin.layouts.app')->title('Online Payment - PG Transactions');
                                                          }
                                                      
                                                      
                                                          protected function authorizeAdminUser()
                                                          {
                                                              $user = Auth::guard('admin')->user();
                                                              if (!$user || !$user->hasAnyRole(['super-admin', 'admin', 'blog-manager'])) {
                                                                  abort(403);
                                                              }
                                                          }
                                                      
                                                      
                                                          protected function getTransactionData(){
                                                              $new_query = PgTransactionModel::with("biller");
                                                              if(!empty($this->filter_search)){
                                                                  $new_query = $new_query->where($this->filter_search_column,"LIKE","%".$this->filter_search."%");
                                                              }
                                                              if($this->filter_status!==""){
                                                                  $new_query = $new_query->where("txn_status",$this->filter_status);
                                                              }         
                                                              if(!empty($this->filter_from_date) && !empty($this->filter_to_date)){
                                                                  $new_query = $new_query->whereBetween('created_at', [$this->filter_from_date, $this->filter_to_date]);
                                                              }    
                                                           
                                                              $records = $new_query->latest()->paginate($this->filter_page_limit);
                                                              return $records;
                                                          }
                                                      }
                                                      
                                                  
                                              
    3. Design the View Open resources\views\livewire\admin\p-g\transactions\index.blade.php file and add this code
                                              
                                              
      
      <div class="page-heading">
          <div class="page-title">
              <div class="row">
                  <div class="col-12 col-md-6 order-md-1 order-last">
                      <h3>PG Transactions History</h3>
                      <p class="text-subtitle text-muted">A pretty helpful component to show emphasized information to the user.</p>
                  </div>
                  <div class="col-12 col-md-6 order-md-2 order-first">
                      <nav aria-label="breadcrumb" class="breadcrumb-header float-start float-lg-end">
                          <ol class="breadcrumb">
                              <li class="breadcrumb-item"><a href="{{ route('user.dashboard') }}" wire:navigate>Dashboard</a></li>
                              <li class="breadcrumb-item active" aria-current="page">Transactions</li>
                          </ol>
                      </nav>
                  </div>
              </div>
          </div>
          <section class="section" id="paginated-section">
              <div class="card">
                  <div class="card-header">
                      <div class="float-start">
                          Transactions List
                      </div>
                  </div>
                  <div class="card-body">
                      <!---Filter section-->
                      <div class="mb-3 row">
                              <div class="col-md-6 mb-2">
                                  <label>Search</label>
                                  <div class="input-group mb-3">
                                      
                                      <button class="btn btn-primary dropdown-toggle" type="button"
                                          data-bs-toggle="dropdown" aria-expanded="false">{{ <?php $filter_search_column_label; ?> }}</button>
                                          <ul class="dropdown-menu">                                  
                                              @foreach ($filter_search_columns[$page] as $key => $type)
                                              <li wire:key="{{ $key }}" value="{{ $type['column'] }}">
                                                  <a class="dropdown-item" href="#" wire:click.prevent="set_filter_search_column('{{ base64_encode($key) }}','{{ $page }}')"> {{ $type['label'] }}</a>
                                              </li>
                                              @endforeach
                                          </ul>                          
                                          <input wire:model="filter_search" type="text" class="form-control">
                                  </div>
                              </div>                          
                              <div class="col-lg-3 mb-2">
                                  <label>Transaction Staus</label>
                                  <select class="form-select" wire:model="filter_status">
                                      <option value="">All</option>
                                      <option value="Pending">Pending</option>
                                      <option value="Successful">Success</option>  
                                      <option value="Failed">Failed</option>                          
                                  </select>
                              </div>
                          </div>
                          <div class="mt-3 row mb-3">  
                              <div class="col-lg-4 mb-2">
                                  <label>Date</label>
                                  <div class="d-flex">
                                      <div class="d-flex align-items-center">
                                          <input type="text" class="form-control datepicker-input"
                                              autocomplete="off"
                                              wire:model="filter_from_date"
                                              name="fromDate" placeholder="Start Date">
                                          <span class="p-h-10 m-2">to</span>
                                          <input type="text" class="form-control datepicker-input"
                                              wire:model="filter_to_date"
                                              autocomplete="off"
                                              name="toDate" placeholder="End Date">
                                      </div>                          
                                  </div>
                              </div>  
                              <div class="col-lg-2 mb-2">
                                  <label>Page Limit</label>
                                  <select class="form-select" wire:model="filter_page_limit">
                                      @foreach ($this->page_limits as $page_limit)
                                      <option wire:key="{{ $page_limit }}" value="{{ $page_limit }}">
                                          {{ $page_limit }}
                                      </option>
                                      @endforeach                    
                                  </select>
                              </div>
                              <div class="col-md-4 mb-2 mt-4">
                                  <button wire:click="filterRender" class="btn btn-primary">Search</button>
                                  <button wire:click="resetFilter('{{ $page }}')" class="btn btn-light">Reset Search</button>
                                  <button wire:click="generateReport" class="btn icon btn-light" data-bs-toggle="tooltip" title="Generate Report"><i class="bi bi-download text-blue"></i></button>
                              </div>                            
                      </div>
                  
                      <!---END :: Filter section-->
                      <div class="table-responsive">
                      <table class="table table-striped table-bordered">
                          <thead>
                              <tr>
                                  <th scope="col">Biller</th>
                                  <th scope="col">Transaction ID</th>
                                  <th scope="col">Order ID </th>
                                  <th scope="col">Amount</th>
                                  <th scope="col">Date</th>
                                  <th scope="col">Status</th>                     
                              
                              </tr>
                          </thead>
                          <tbody>
                              @forelse ($component_records as $record)
                                      <tr wire:key="{{ $record->id }}">                              
                                      <td>
                                          <span  class="text-sm"> {{ $record->biller->biller_category }}</span></br>
                                          <span  class="text-sm"> {{ $record->biller->biller_name }} </span>
                                      </td>                              
                                      <td>{{ $record->txn_ref_id }}</td>
                                      <td>{{ $record->order_id }}</td>                            
                                      <td>{{ $records_info['currency'] ?? 'INR' }} {{ $record->amount }}</td>                              
                                      <td>{{ $record->txn_date }}</td>                              
                                      <td>
                                          @if ($record->txn_status == 'SUCCESS')
                                      <span class="badge bg-light-success">Success</span>
                                      @elseif($record->txn_status == 'FAILED')
                                      <span class="badge bg-light-danger">Failed</span>
                                      @else
                                      <span class="badge bg-light-secondary">Pending</span>
                                      @endif
                                      </span>
                                      </td>                            
                                  </tr>
                                  @empty
                                      <td colspan="7">
                                          <span class="text-danger">
                                              <strong>Record does not exist.</strong>
                                          </span>
                                      </td>
                              @endforelse
                          </tbody>
                      </table>
                  </div>
                      {{ $component_records->links(data: ['scrollTo' => '#paginated-section']) }}
                  </div>
              </div>
          </section>                                              
      </div>
      @script
      
          flatpickr('.datepicker-input', {
              dateFormat: "Y-m-d",
          })
      
      @endscript
                                                      
                                                  
                                                  
                                              

    Step 5: Set Up Routes

    Add a route: Open routes/web.php and define the transaction route:

                                        
                                            use App\Livewire\Admin\{PG}; 
                                            //PG Transaction
                                            Route::get('/pg/transactions', PG\Transactions\Index::class)->name('pg.transactions');
                                        
                                    

    Step 6: Run the Project and Navigate to the Browser

    1. Serve the application locally:
                                                  
                                                      php artisan serve
                                                  
                                              

      This will start the server and provide you with a local URL to access your application.

    2. Access the Component in the Browser Open your preferred web browser and navigate to:

    Conclusion

    In conclusion, creating a reusable filter component in Laravel Livewire is a powerful way to streamline your development process. By implementing the FilterTrait, you can centralize filter logic, enhance code reusability, and reduce redundancy across components. We hope this step-by-step guide has been insightful. Feel free to share your feedback or thoughts on this approach—your input is always appreciated!

    Did this solution work for you? Drop a like or comment below!

    Satish Parmar

    Satish Parmar

    Experienced Full-Stack Web Developer

    I'm a passionate full-stack developer and blogger from India, dedicated to sharing web development tips and solutions. As the creator of TipInfoTrove.com, my goal is to help developers and tech enthusiasts solve real-world challenges with expertise in PHP, Laravel, JavaScript, Vue, React, and more. Through detailed guides and practical insights, I strive to empower others to excel in their projects and stay ahead in the ever-evolving world of technology.

    0 Comments

    Post Comment

    Your email address will not be published. Required fields are marked *