You are using an older browser that might negatively affect how this site is displayed. Please update to a modern browser to have a better experience. Sorry for the inconvenience!

Infinite Loading of Data Table in Lightning Web Component


In Lightning Web component, we can develop Lightning data table with infinite loading capability. This will help users to see more records whenever they scroll to the bottom of the table rows, querying more every time the user scrolls to the end rather than querying all records at once. This will also replace pagination where users must click next/previous button to see more records.  

Cons of Using Pagination: 

  • Requires writing up complex lines of code 
  • Need to develop buttons for next, previous, first page and last page and alignment of these elements 
  • Need to develop events and functions for each of the above buttons 
  • Users need to click each button every time they need to navigate 

Features: 

  • Faster load performance due to a smaller number of records are loaded for each scroll event 
  • Loading spinner at the end of the table to indicate records are being fetched. 
  • Toast messages on exceptions and when all records are loaded. 
  • Displaying record count for each scroll event, respectively. 
  • Fetches records from server every time (every load more). 

Common issues faced during development: 

  • Scroll event getting called multiple times for a single end scroll 
  • Scroll event getting called along with the constructor on Page load resulting in 2X initial rows 
  • Load more results being concatenated incorrectly or duplicated results 

The following code will resolve the above known issues that occurs more often,  

The HTML Code (InfiniteTable.html) 

  1. <template>   
  2.     <lightning-card>   
  3.         <h3 slot=“title”>   
  4.             <lightning-icon icon-name=“utility:user” size=“small”></lightning-icon>   
  5.               Infinite Loading Account List   
  6.             <div style=“float:right”>   
  7.                 <template if:true={disableLoadMore}>   
  8.                     Showing All   
  9.                 </template>   
  10.                 <template if:false={disableLoadMore}>   
  11.                     Showing {currentCount} of {totalRecordCount}   
  12.                 </template>   
  13.             </div>   
  14.         </h3>   
  15.         <div style=“height:310px”>   
  16.         <!–Lightning data table markup–>   
  17.             <lightning-datatable   
  18.                 data={accountData}   
  19.                 columns={accountColumns}   
  20.                 key-field=“Id”   
  21.                 hide-checkbox-column   
  22.                 show-row-number-column   
  23.                 enable-infinite-loading   
  24.                 onloadmore={handleLoadMore}   
  25.             >   
  26.             </lightning-datatable>   
  27.         </div>   
  28.         <div slot=“footer”>   
  29.             {loadMoreStatus} {error}   
  30.         </div>   
  31.     </lightning-card>   
  32. </template>   

JavaScript (InfiniteTable.js) 

  1. import { LightningElement, track } from lwc;   
  2. import fetchAccountRecords from ‘@salesforce/apex/InfiniteTableController_AC.fetchAccountRecords’;   
  3. import { ShowToastEvent } from ‘lightning/platformShowToastEvent’;   
  4.    
  5. export defaultclassInfinteTableextendsLightningElement {   
  6.     @track _accountData = [];   
  7.     @track error;   
  8.     @trackloadMoreStatus = ;   
  9.     queryOffset;   
  10.     currentCount = 0;   
  11.     queryLimit;   
  12.     totalRecordCount;   
  13.     timesCalled = 0;   
  14.     disableLoadMore = false;   
  15.     fromConstructor;   
  16.     @trackaccountColumns = [{   
  17.             label     : “Account Name”,   
  18.             fieldName : “Name”,   
  19.             type      : “text”   
  20.         }, {   
  21.             label     : “Rating”,   
  22.             fieldName : “Rating”,   
  23.             type      : “text”   
  24.         }, {   
  25.             label     : “Account Source”,   
  26.             fieldName : AccountSource,   
  27.             type      : “text”   
  28.         }, {   
  29.             label     : “Created By”,   
  30.             fieldName : CreatedByName,   
  31.             type      : “text”   
  32.         }, {   
  33.             label     : “Created Date”,   
  34.             fieldName : “CreatedDate”,   
  35.             type      : “date”,   
  36.             typeAttributes : {   
  37.                 year: “numeric”,   
  38.                 month: “long”,   
  39.                 day: “2-digit”,   
  40.                 hour: “2-digit”,   
  41.                 minute: “2-digit”   
  42.             }   
  43.         }   
  44.     ];   
  45.    
  46.     constructor() {   
  47.         super();   
  48.         this.queryOffset = 0;   
  49.         this.queryLimit = 10;   
  50.         this.fromConstructor = true;   
  51.         this.fetchRecords();   
  52.         this.currentCount = 10;   
  53.         console.log(‘Called from Infinite Table Constructor ’+this.queryOffset);   
  54.     }   
  55.    
  56.     showToast(type, title, message, variant, mode) {   
  57.         constevt = newShowToastEvent({   
  58.             type : type,   
  59.             title: title,   
  60.             message: message,   
  61.             variant: variant,   
  62.             mode: mode   
  63.         });   
  64.         this.dispatchEvent(evt);   
  65.     }   
  66.    
  67.     handleLoadMore(event) {   
  68.         if(this.disableLoadMore)   
  69.             return;   
  70.         if(this.fromConstructor == true) {   
  71.             this.fromConstructor = false;   
  72.             return;   
  73.         }   
  74.         const { target } = event;   
  75.         //Display a spinner to signal that data is being loaded   
  76.         target.isLoading = true;   
  77.         if(this.totalRecordCount > this.queryOffset) {   
  78.             this.queryOffset = this.queryOffset + 10;   
  79.             this.fetchRecords()   
  80.             .then(()=> {   
  81.                 target.isLoading = false;   
  82.                 if(this.totalRecordCount > (this.currentCount + 10))   
  83.                     this.currentCount = this.currentCount + 10;   
  84.                 else   
  85.                     this.currentCount = this.totalRecordCount;   
  86.             });   
  87.         }   
  88.         else {   
  89.             target.isLoading = false;   
  90.             this.disableLoadMore = true;   
  91.             this.loadMoreStatus = ‘No more to load.’;   
  92.             this.showToast(‘Success’, ‘Success’, ‘All Account Records are Loaded!’, ‘success’, ‘dismissible’);   
  93.         }   
  94.         console.log(‘Called from Infinite Table Handle More ’+this.queryOffset);   
  95.     }   
  96.    
  97.     fetchRecords() {   
  98.         let newData;   
  99.         returnfetchAccountRecords({   
  100.             queryLimit : this.queryLimit,    
  101.             queryOffset: this.queryOffset   
  102.         })   
  103.         .then(result => {    
  104.             this.totalRecordCount = result.totalRecordCount;   
  105.             newData = JSON.parse(JSON.stringify(result.accRecords));   
  106.             newData.forEach(function(entry) {   
  107.                 // flatten the data to that it can be directly consumed by data table   
  108.                 if (entry.CreatedBy){   
  109.                     entry.CreatedByName = entry.CreatedBy.Name;   
  110.                 }   
  111.             });   
  112.             let newRecords = […this._accountData, …newData];   
  113.             this._accountData = newRecords;   
  114.             this.error = undefined;   
  115.         })   
  116.         .catch(error => {    
  117.             this.error = error;   
  118.             this.showToast(‘Error’, ‘Error’, ‘Error getting records from Server’, ‘error’, ‘sticky’);   
  119.         })   
  120.     }   
  121.    
  122.     get accountData() {   
  123.         returnthis._accountData.length ? this._accountData : null;   
  124.     }   
  125. }   

Apex Controller (InfiniteTableController_AC.cls) 

  1. public with sharing classInfiniteTableController_AC {   
  2.    
  3.     @AuraEnabled(cacheable = true)   
  4.     publicstatic AccountWrapper fetchAccountRecords(Integer queryLimit, Integer queryOffset) {   
  5.         returnnew AccountWrapper([SELECT count() FROM Account],   
  6.         [SELECT Name, CreatedBy.Name, Rating, AccountSource, CreatedDate    
  7.                 FROM Account   
  8.                 ORDER BY CreatedDate DESC    
  9.                 LIMIT :queryLimit    
  10.                 OFFSET :queryOffset]);   
  11.     }   
  12.    
  13.     publicclassAccountWrapper {   
  14.         @AuraEnabled   
  15.         public Integer totalRecordCount { get; set; }   
  16.         @AuraEnabled   
  17.         public List<Account> accRecords { get; set; }   
  18.         public AccountWrapper(Integer totalRecordCount, List<Account> accRecords) {   
  19.             this.totalRecordCount = totalRecordCount;   
  20.             this.accRecords = accRecords;   
  21.         }   
  22.     }   
  23. }   

Output: 

When Page Loads, 

 

When all records are scrolled and loaded, 

 

Conclusion: 

The Infinite Loading data table can reduce a lot of manual code that we used to build for Tables with Paginations. Combining with LWC, the table view development can work faster.