CustomEvent dispatches.
Displaying hundreds of records at once on a Salesforce page slows down browser performance and overwhelms users. A dedicated child paginator component cleanly separates the user interface navigation controls from data retrieval logic, allowing you to reuse the exact same pagination toolbar across lists, data tables, and custom cards.
1. Component Architecture & Data Flow
The paginator architecture follows standard reactive parent-child communication patterns in LWC:
- Parent-to-Child Data (
@apiProperties): The parent component passescurrentPage,totalItems, andpageSizedown into the child paginator. - Child Computed State: Getters in the child determine total pages and compute disabled button states (
isFirstPage,isLastPage) to prevent out-of-bounds navigation. - Child-to-Parent Notification (
CustomEvent): When a user clicks any navigation button, the child dispatches apagechangeevent carrying the targeted page number inevent.detail.
- Child Inputs:
@api currentPage,@api totalItems,@api pageSize. - Event Dispatch:
new CustomEvent('pagechange', { detail: newPageNumber }). - Parent Action: Listens via
onpagechange={handlePageChange}and fetches or slices data for the active page. - UI Standard: Salesforce Lightning Design System (SLDS) button groups for responsive alignment.
2. Implementing the Child Paginator Component
Below is the complete implementation for the reusable child component, styled cleanly with SLDS utilities.
paginator.html)
<template>
<div class="slds-align_absolute-center slds-m-vertical_medium">
<lightning-button-group>
<lightning-button
label="First"
icon-name="utility:jump_to_left"
disabled={isFirstPage}
onclick={handleFirstPage}>
</lightning-button>
<lightning-button
label="Previous"
icon-name="utility:chevronleft"
disabled={isFirstPage}
onclick={handlePreviousPage}>
</lightning-button>
</lightning-button-group>
<span class="slds-p-horizontal_medium slds-text-body_regular">
Page <strong>{currentPage}</strong> of <strong>{totalPages}</strong>
</span>
<lightning-button-group>
<lightning-button
label="Next"
icon-name="utility:chevronright"
icon-position="right"
disabled={isLastPage}
onclick={handleNextPage}>
</lightning-button>
<lightning-button
label="Last"
icon-name="utility:jump_to_right"
icon-position="right"
disabled={isLastPage}
onclick={handleLastPage}>
</lightning-button>
</lightning-button-group>
</div>
</template>
paginator.js)
import { LightningElement, api } from 'lwc';
export default class Paginator extends LightningElement {
@api currentPage = 1;
@api totalItems = 0;
@api pageSize = 10;
get totalPages() {
if (!this.totalItems || !this.pageSize) {
return 1;
}
return Math.ceil(this.totalItems / this.pageSize);
}
get isFirstPage() {
return this.currentPage <= 1;
}
get isLastPage() {
return this.currentPage >= this.totalPages;
}
handleFirstPage() {
if (!this.isFirstPage) {
this.dispatchPageChangeEvent(1);
}
}
handlePreviousPage() {
if (!this.isFirstPage) {
this.dispatchPageChangeEvent(this.currentPage - 1);
}
}
handleNextPage() {
if (!this.isLastPage) {
this.dispatchPageChangeEvent(this.currentPage + 1);
}
}
handleLastPage() {
if (!this.isLastPage) {
this.dispatchPageChangeEvent(this.totalPages);
}
}
dispatchPageChangeEvent(targetPage) {
if (targetPage !== this.currentPage) {
const pageChangeEvent = new CustomEvent('pagechange', {
detail: targetPage
});
this.dispatchEvent(pageChangeEvent);
}
}
}
3. Embedding the Paginator in a Parent Component
The parent component maintains the master dataset, slices or queries data for the active page, and responds to the child's pagechange event.
paginationContainer.html)
<template>
<lightning-card title="Account Directory" icon-name="standard:account">
<div class="slds-p-around_medium">
<!-- Render your paged data table or custom cards -->
<lightning-datatable
key-field="Id"
data={displayedRecords}
columns={columns}
hide-checkbox-column>
</lightning-datatable>
<!-- Reusable Child Paginator -->
<c-paginator
current-page={currentPage}
total-items={totalRecords}
page-size={pageSize}
onpagechange={handlePageChange}>
</c-paginator>
</div>
</lightning-card>
</template>
paginationContainer.js)
import { LightningElement, track } from 'lwc';
const COLUMNS = [
{ label: 'Name', fieldName: 'Name', type: 'text' },
{ label: 'Industry', fieldName: 'Industry', type: 'text' },
{ label: 'Phone', fieldName: 'Phone', type: 'phone' }
];
export default class PaginationContainer extends LightningElement {
columns = COLUMNS;
@track allRecords = [];
@track displayedRecords = [];
currentPage = 1;
pageSize = 5;
get totalRecords() {
return this.allRecords.length;
}
connectedCallback() {
this.loadSampleData();
this.updatePageData();
}
loadSampleData() {
const records = [];
for (let i = 1; i <= 32; i++) {
records.push({
Id: `ACC-${i}`,
Name: `United Partners Corp ${i}`,
Industry: i % 2 === 0 ? 'Technology' : 'Finance',
Phone: `(555) 019-${1000 + i}`
});
}
this.allRecords = records;
}
handlePageChange(event) {
this.currentPage = event.detail;
this.updatePageData();
}
updatePageData() {
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
this.displayedRecords = this.allRecords.slice(startIndex, endIndex);
}
}
4. Common Traps & Best Practices
If a user is on page 5 and performs a search that narrows total records down to 3, the component will remain on page 5 and display an empty screen. Always reset
this.currentPage = 1 whenever the master dataset is filtered.
Array.slice()) or server queries in the parent.
- Client vs. Server-Side Processing: Use client-side array slicing for datasets under 1,000 rows. For high-volume enterprise datasets, query Apex with
OFFSETor keyset pagination on every page change. - Use Built-in SLDS Classes: Use standard Lightning Design System classes (
slds-align_absolute-center,slds-button-group) rather than writing custom margin and alignment CSS. - Guard Against Division by Zero: Always ensure
totalPagesdefaults to at least 1 when datasets are empty to avoid renderingPage 1 of 0.
Summary
Decoupling pagination controls into a standalone child component creates a reusable, scalable architecture for Lightning Web Components. By combining standard @api properties, reactive getters, and CustomEvents, developers can deliver clean, responsive record navigation across all Salesforce applications.