Skip to main content

How to Keep LWC Datatable Rows Selected During Pagination

In plain words: When you use a paginated <lightning-datatable> in Salesforce, clicking "Next Page" completely refreshes the table's data. Because of this, any rows the user checked on Page 1 will be forgotten by the time they get back. To fix this, we must write custom JavaScript to save the IDs of selected rows into a "master list" behind the scenes, and then re-apply those selections every time the page changes.

Displaying large datasets in Lightning Web Components (LWC) requires pagination to maintain performance. However, out-of-the-box, the lightning-datatable component does not remember selected rows when the underlying data changes.

In this tutorial, we will build a custom datatable with "Previous" and "Next" pagination buttons that flawlessly remembers user selections across pages.

Step 1: Set Up the Project

First, create a new Lightning Web Component in your Salesforce DX project using the Salesforce CLI.

sf lightning generate component -n dataTableWithPagination -d force-app/main/default/lwc

Step 2: The HTML Markup

Open the dataTableWithPagination.html file. We need to define the datatable and wire up our pagination buttons. Notice that we are binding the selected-rows attribute to a JavaScript array to control which boxes are checked.

<template>
  <lightning-card title="Paginated Datatable with Persistent Selection">
    
    <div class="slds-m-around_medium">
        <lightning-datatable
          key-field="Id"
          data={currentPageData}
          columns={columns}
          selected-rows={selectedRowIds}
          onrowselection={handleRowSelection}>
        </lightning-datatable>
    </div>

    <!-- Pagination Controls -->
    <div class="slds-m-around_medium slds-grid slds-grid_align-spread">
        <lightning-button
          label="Previous"
          onclick={handlePrevious}
          variant="brand"
          disabled={disablePrevious}>
        </lightning-button>
        
        <span class="slds-align_absolute-center">Page {pageNumber} of {totalPages}</span>

        <lightning-button
          label="Next"
          onclick={handleNext}
          variant="brand"
          disabled={disableNext}>
        </lightning-button>
    </div>

  </lightning-card>
</template>

Step 3: The JavaScript Logic (State Management)

Open dataTableWithPagination.js. The key to making this work is separating the visible rows on the current page from the master list of all selected rows.

import { LightningElement, track } from 'lwc';

export default class DataTableWithPagination extends LightningElement {
  
  @track allData = [];          // All records fetched from Apex
  @track currentPageData = [];  // Records currently displayed on the screen
  @track selectedRowIds = [];   // Master list of selected IDs across ALL pages
  
  columns = [
      { label: 'Name', fieldName: 'Name' },
      { label: 'Industry', fieldName: 'Industry' }
  ];

  // Pagination state
  pageNumber = 1;
  pageSize = 5; 
  totalPages = 0;

  get disablePrevious() {
      return this.pageNumber === 1;
  }

  get disableNext() {
      return this.pageNumber >= this.totalPages;
  }

  connectedCallback() {
      this.fetchData();
  }

  // Mock data fetch (Replace with actual Apex call)
  fetchData() {
      // Simulate fetching 12 records
      this.allData = Array.from({ length: 12 }, (v, i) => ({
          Id: `001_Id_${i}`,
          Name: `Acme Corp ${i}`,
          Industry: 'Technology'
      }));
      
      this.totalPages = Math.ceil(this.allData.length / this.pageSize);
      this.updatePagination();
  }

  // The Magic: Handling Row Selection
  handleRowSelection(event) {
      // 1. Get IDs of rows selected currently on the screen
      const visibleSelectedIds = event.detail.selectedRows.map(row => row.Id);
      
      // 2. Get IDs of ALL rows currently visible on the screen
      const visibleRowIds = this.currentPageData.map(row => row.Id);

      // 3. Identify previously selected rows that are hidden on other pages
      const hiddenSelectedIds = this.selectedRowIds.filter(id => !visibleRowIds.includes(id));

      // 4. Merge hidden selections with the new visible selections
      this.selectedRowIds = [...hiddenSelectedIds, ...visibleSelectedIds];
  }

  handlePrevious() {
      if (this.pageNumber > 1) {
          this.pageNumber--;
          this.updatePagination();
      }
  }

  handleNext() {
      if (this.pageNumber < this.totalPages) {
          this.pageNumber++;
          this.updatePagination();
      }
  }

  // Slice the full array to get the current page's data
  updatePagination() {
      const startIndex = (this.pageNumber - 1) * this.pageSize;
      const endIndex = this.pageNumber * this.pageSize;
      
      this.currentPageData = this.allData.slice(startIndex, endIndex);
  }
}
Developer Trap: Datatable selected-rows Array Type
The selected-rows property in HTML expects an array of strings (the IDs matching your key-field), not an array of objects! If you try to push full record objects into this.selectedRowIds, the checkboxes will not render properly when you change pages.
360 Card: The Persistence Logic
How the handleRowSelection method works:
  • Page 1: User selects 2 records. selectedRowIds holds 2 IDs.
  • Page 2: The user navigates. The 2 selected records from Page 1 are now "hidden." The code filters them out and saves them safely.
  • Page 2 Selection: The user selects 1 record on Page 2. The code merges the 2 hidden IDs with the 1 new visible ID.
  • Result: selectedRowIds perfectly tracks 3 total selections across both pages.
Core Takeaway: To keep rows selected during pagination, you must manually capture the selected IDs, separate the ones visible on the current page from the ones hidden on other pages, and merge them together in the onrowselection event.

Conclusion

Losing user selections when clicking "Next Page" is a frustrating experience for end users. By implementing manual state management in your JavaScript controller, you can build a smart, paginated datatable that securely tracks every selection made across your entire dataset.

Happy coding!