<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);
}
}
selected-rows Array TypeThe
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.
How the
handleRowSelection method works:
- Page 1: User selects 2 records.
selectedRowIdsholds 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:
selectedRowIdsperfectly tracks 3 total selections across both pages.
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!