<lightning-datatable> has a major quirk: if a user selects a few rows, types a word into a search bar to filter the list, and then clears the search, their original selections disappear! To fix this, you must write custom JavaScript to temporarily store the IDs of selected rows that are "hidden" by the filter, and merge them back when the filter is cleared.
Displaying data in a tabular format is a standard requirement for almost any enterprise application. In Salesforce Lightning Web Components (LWC), the lightning-datatable component makes this easy. However, combining row selection with dynamic front-end filtering introduces a frustrating state-management problem.
Because filtering replaces the data bound to the datatable, any previously selected records that no longer match the search term are "forgotten" by the component. Let's walk through how to build a smart, stateful datatable that remembers row selections no matter how many times the user filters the data.
Step 1: Set Up the Project
First, create a new Lightning Web Component in your Salesforce DX project using the modern Salesforce CLI.
sf lightning generate component -n persistentDatatable -d force-app/main/default/lwc
Step 2: The HTML Template
Open the persistentDatatable.html file. We need two main elements: a lightning-input to act as our search bar, and the lightning-datatable to display the filtered results. Crucially, we must bind the selected-rows attribute to a JavaScript array.
<template>
<lightning-card title="Smart Datatable Selection" icon-name="standard:account">
<!-- Search Bar -->
<div class="slds-m-around_medium">
<lightning-input
type="search"
label="Filter Accounts"
onchange={handleSearch}>
</lightning-input>
</div>
<!-- Datatable -->
<div class="slds-m-around_medium">
<lightning-datatable
key-field="Id"
data={filteredData}
columns={columns}
selected-rows={selectedRowIds}
onrowselection={handleRowSelection}>
</lightning-datatable>
</div>
</lightning-card>
</template>
Step 3: The JavaScript Logic (State Management)
This is where the heavy lifting happens. Open persistentDatatable.js. We need to maintain a "master list" of selected IDs. Every time the user checks or unchecks a box, we merge the visible selections with the hidden selections.
import { LightningElement, track, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts'; // Example Apex Method
export default class PersistentDatatable extends LightningElement {
@track fullData = []; // Holds all records from the database
@track filteredData = []; // Holds only the records matching the search
@track selectedRowIds = []; // Master list of selected IDs
columns = [
{ label: 'Name', fieldName: 'Name' },
{ label: 'Industry', fieldName: 'Industry' }
];
// 1. Fetch data from Apex
@wire(getAccounts)
wiredData({ error, data }) {
if (data) {
this.fullData = data;
this.filteredData = data; // Initially, filtered data is all data
} else if (error) {
console.error('Error fetching data:', error);
}
}
// 2. Handle the Search Input
handleSearch(event) {
const searchTerm = event.target.value.toLowerCase();
// Filter the full dataset
this.filteredData = this.fullData.filter(record => {
return record.Name.toLowerCase().includes(searchTerm);
});
}
// 3. The Magic: Handling Row Selection
handleRowSelection(event) {
// Step A: Get the IDs of the rows currently selected on the screen
const visibleSelectedIds = event.detail.selectedRows.map(row => row.Id);
// Step B: Get the IDs of ALL rows currently visible on the screen
const visibleRowIds = this.filteredData.map(row => row.Id);
// Step C: Identify previously selected rows that are currently hidden by the filter
const hiddenSelectedIds = this.selectedRowIds.filter(id => !visibleRowIds.includes(id));
// Step D: Merge the hidden selections with the visible selections
this.selectedRowIds = [...hiddenSelectedIds, ...visibleSelectedIds];
}
}
Notice how we map the selected rows down to just their
Ids? The selected-rows property expects an array of strings (the key-field values), not an array of objects. Passing full objects into selectedRowIds will cause the checkboxes to fail silently!
If you have 10 records and select 2.
- Filter applied: 5 records show. The 2 selected records disappear.
- Hidden Selections: Our code realizes the 2 selected IDs are no longer in the
visibleRowIdsarray, so it saves them in a safe place (hiddenSelectedIds). - New Selections: The user selects 1 more record from the filtered 5.
- The Merge: The code combines the 2 hidden IDs with the 1 visible ID, resulting in an accurate master array of 3 selections.
onrowselection event fires.
Conclusion
While the standard Lightning Datatable does not handle persistent selections out-of-the-box during filtering, implementing your own state management in JavaScript is highly rewarding. By understanding array filtering and the spread operator (...), you can provide a seamless, robust user experience that doesn't unexpectedly delete your users' hard work.
Happy coding!