React Pagination - Getting Started
jQuery
Angular
Vue
React
Pagination is a UI component that allows users to navigate through pages and change page size at runtime.
This tutorial explains how to add Pagination to a page and configure the component's core settings. It also covers implementing remote pagination. Colored cards are loaded each time a user switches pages or changes page size. The final result is displayed below:
Each section in this tutorial describes a single configuration step. You can also find the full source code in the following GitHub repository:
Create Pagination
jQuery
Add DevExtreme to your jQuery application and use the following code to create a Pagination component:
$(function() {
$("#pagination").dxPagination({ });
});
<html>
<head>
<!-- ... -->
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/devextreme@25.2-next/dist/css/dx.light.css">
<script type="text/javascript" src="https://unpkg.com/devextreme@25.2-next/dist/js/dx.all.js"></script>
<script type="text/javascript" src="index.js"></script>
</head>
<body>
<div id="pagination"></div>
</body>
</html>Angular
Add DevExtreme to your Angular application and use the following code to create a Pagination component:
<dx-pagination></dx-pagination>
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { DxPaginationModule } from 'devextreme-angular';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
DxPaginationModule
],
providers: [ ],
bootstrap: [AppComponent]
})
export class AppModule { }Vue
Add DevExtreme to your Vue application and use the following code to create a Pagination component:
<template>
<DxPagination />
</template>
<script setup lang="ts">
import 'devextreme/dist/css/dx.light.css';
import { DxPagination } from 'devextreme-vue/pagination';
</script>React
Add DevExtreme to your React application and use the following code to create a Pagination component:
import React from 'react';
import 'devextreme/dist/css/dx.light.css';
import { Pagination } from 'devextreme-react/pagination';
function App(): JSX.Element {
return (
<Pagination />
);
}
export default App;Configure Pagination
This tutorial step guides you through the basic Pagination setup.
Specify the following settings:
- itemCount sets the total number of items. Pagination does not function properly without this setting.
- pageIndex sets the initial page to display. This tutorial sets pageIndex to 3 (the default value is 1).
- allowedPageSizes specifies page sizes available to users. Modify this list as needed. Include
'all'to allow users to display all items on one page. This tutorial uses the default value:[5, 10]. - pageSize specifies the initial page size.
The following code snippet demonstrates how to apply the aforementioned settings:
jQuery
const total = 100;
$(() => {
const pagination = $('#pagination')
.dxPagination({
showInfo: true,
showNavigationButtons: true,
itemCount: total,
pageIndex: 3,
pageSize: 5,
})
.dxPagination('instance');
});Angular
<dx-pagination
[showInfo]="true"
[showNavigationButtons]="true"
[itemCount]="total"
[pageIndex]="pageIndex"
[pageSize]="pageSize"
>
</dx-pagination>
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
total = 100;
pageIndex = 3;
pageSize = 5;
}Vue
<template>
<DxPagination
:show-info="true"
:show-navigation-buttons="true"
v-model:page-index="pageIndex"
v-model:page-size="pageSize"
:item-count="total"
/>
</template>
<script setup lang="ts">
// ...
import { ref } from 'vue';
const total = 100;
const pageSize = ref(5);
const pageIndex = ref(3);
</script>React
import React, { useState } from 'react';
// ...
const total = 100;
function App(): JSX.Element {
const [pageSize, setPageSize] = useState<number>(5);
const [pageIndex, setPageIndex] = useState<number>(3);
return (
<Pagination
showInfo={true}
showNavigationButtons={true}
pageIndex={pageIndex}
pageSize={pageSize}
itemCount={total}
/>
);
}
export default App;Implement Remote Pagination
This section explains how to implement remote pagination. Client code generates a color list and requests a remote service for cards representing color entries on the screen. The pagination component helps users browse the resulting color cards.
Implementation can be broken down into three steps:
- Generate 100 hex codes.
- Fetch color cards from The Color API service when necessary:
- On page load
- On page size changes
- On page index changes
- Display color cards obtained from the service.
Implement the first step. Generate 100 random pastel hex codes and add them to an array:
jQuery
const hexCodes = [];
const getRandomPastelColor = () => {
const hue = Math.floor(Math.random() * 360);
const saturation = Math.random() * 0.4 + 0.2;
const brightness = Math.random() * 0.3 + 0.7;
return hsvToHex(hue, saturation, brightness);
};
const hsvToHex = (h, s, v) => {
let r = 0;
let g = 0;
let b = 0;
const i = Math.floor(h / 60);
const f = h / 60 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
const toHex = (x) => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
return toHex(r) + toHex(g) + toHex(b);
};
$(() => {
for (let i = 0; i < total; i += 1) {
hexCodes.push(getRandomPastelColor());
}
const pagination = $('#pagination')
.dxPagination({
// ...
})
.dxPagination('instance');
});Angular
import { Component } from '@angular/core';
import { ColorService } from './app.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [ColorService],
})
export class AppComponent {
// ...
hexCodes: string[] = [];
constructor(private readonly colorService: ColorService) {}
ngOnInit(): void {
this.generateHexCodes();
}
generateHexCodes(): void {
for (let i = 0; i < this.total; i++) {
this.hexCodes.push(this.colorService.getRandomPastelColor());
}
}
}
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ColorService {
private hsvToHex(h: number, s: number, v: number): string {
let r = 0;
let g = 0;
let b = 0;
const i = Math.floor(h / 60);
const f = h / 60 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: [r, g, b] = [v, t, p]; break;
case 1: [r, g, b] = [q, v, p]; break;
case 2: [r, g, b] = [p, v, t]; break;
case 3: [r, g, b] = [p, q, v]; break;
case 4: [r, g, b] = [t, p, v]; break;
case 5: [r, g, b] = [v, p, q]; break;
}
const toHex = (x: number): string => Math.round(x * 255).toString(16).padStart(2, '0');
return `${toHex(r)}${toHex(g)}${toHex(b)}`;
}
getRandomPastelColor(): string {
const hue = Math.floor(Math.random() * 360);
const saturation = Math.random() * 0.4 + 0.2;
const brightness = Math.random() * 0.3 + 0.7;
return this.hsvToHex(hue, saturation, brightness);
}
}Vue
<template>
<!-- ... -->
</template>
<script setup lang="ts">
// ...
import { ref, onMounted } from 'vue';
import { getRandomPastelColor } from '../assets/colorService';
const hexCodes = ref<string[]>([]);
const generateHexCodes = () => {
for (let i = 0; i < total; i++) {
hexCodes.value.push(getRandomPastelColor());
}
};
onMounted(() => {
generateHexCodes();
});
</script>
export const getRandomPastelColor = (): string => {
const hue = Math.floor(Math.random() * 360);
const saturation = Math.random() * 0.4 + 0.2;
const brightness = Math.random() * 0.3 + 0.7;
return hsvToHex(hue, saturation, brightness);
};
const hsvToHex = (h: number, s: number, v: number): string => {
let r: number, g: number, b: number;
const i = Math.floor(h / 60) % 6;
const f = h / 60 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i) {
case 0: [r, g, b] = [v, t, p]; break;
case 1: [r, g, b] = [q, v, p]; break;
case 2: [r, g, b] = [p, v, t]; break;
case 3: [r, g, b] = [p, q, v]; break;
case 4: [r, g, b] = [t, p, v]; break;
case 5: [r, g, b] = [v, p, q]; break;
default: throw new Error("Unexpected case in HSV to RGB conversion");
}
const toHex = (x: number): string => Math.round(x * 255).toString(16).padStart(2, '0');
return `${toHex(r)}${toHex(g)}${toHex(b)}`;
};React
import React, { useState, useEffect } from 'react';
import { getRandomPastelColor } from './colorService';
// ...
function App(): JSX.Element {
// ...
const hexCodes = useRef<string[]>([]);
useEffect(() => {
for (let i = 0; i < total; i++) {
hexCodes.current.push(getRandomPastelColor());
}
}, []);
return (
<!-- ... -->
);
}
export default App;
export const getRandomPastelColor = (): string => {
const hue = Math.floor(Math.random() * 360);
const saturation = Math.random() * 0.4 + 0.2;
const brightness = Math.random() * 0.3 + 0.7;
return hsvToHex(hue, saturation, brightness);
};
const hsvToHex = (h: number, s: number, v: number): string => {
let r: number, g: number, b: number;
const i = Math.floor(h / 60) % 6;
const f = h / 60 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
switch (i) {
case 0: [r, g, b] = [v, t, p]; break;
case 1: [r, g, b] = [q, v, p]; break;
case 2: [r, g, b] = [p, v, t]; break;
case 3: [r, g, b] = [p, q, v]; break;
case 4: [r, g, b] = [t, p, v]; break;
case 5: [r, g, b] = [v, p, q]; break;
default: throw new Error("Unexpected case in HSV to RGB conversion");
}
function toHex(x: number): string {
if (isNaN(x) || x < 0 || x > 1) {
return '00';
}
return Math.round(x * 255).toString(16).padStart(2, '0');
}
return `${toHex(r)}${toHex(g)}${toHex(b)}`;
};Fetch Data
The following code snippet demonstrates how to fetch data from the Color API:
jQuery
// ...
const apiEndpoint = 'https://www.thecolorapi.com/id?hex=';
const cache = new Map();
function fetchData(colorId) {
return new Promise((resolve, reject) => {
if (cache.has(colorId)) {
resolve(cache.get(colorId));
} else {
$.getJSON(apiEndpoint + colorId, (data) => {
const colorData = {
image: data.image.bare,
name: data.name.value,
};
cache.set(colorId, colorData);
resolve(colorData);
}).fail(() => {
reject(new Error(`Error loading color for hex: ${colorId}`));
});
}
});
}
$(() => {
// ...
const pagination = $('#pagination')
.dxPagination({
// ...
})
.dxPagination('instance');
});Angular
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ColorService {
// ...
fetchColorData(hex: string): Observable<Color> {
return this.http.get<any>(`${this.apiEndpoint}${hex}`)
.pipe(
catchError((error: any) => throwError(() => new Error(`Error fetching color: ${error.message || error}`))),
)
.pipe(map((data: any) => ({
name: data.name.value,
image: data.image.bare,
})));
}
}Vue
import axios from 'axios';
const apiEndpoint = 'https://www.thecolorapi.com/id?hex=';
export const fetchColorData = async (
hex: string,
): Promise<{ name: string; image: string } | null> => {
try {
const response = await axios.get(`${apiEndpoint}${hex}`);
return {
name: response.data.name.value,
image: response.data.image.bare,
};
} catch (error) {
console.error(`Error fetching color for hex ${hex}:`, error);
return null;
}
};React
import axios from 'axios';
const apiEndpoint = 'https://www.thecolorapi.com/id?hex=';
export async function fetchColorData(hex: string): Promise<{ name: string; image: string } | null> {
try {
const response = await axios.get(`${apiEndpoint}${hex}`);
return {
name: response.data.name.value,
image: response.data.image.bare,
};
} catch (error) {
console.error(`Error fetching color for hex ${hex}:`, error);
return null;
}
}Render Items
The render function determines the subset of cards to be displayed and populates the array with images and alt strings.
jQuery
<html>
<head>
<!-- ... -->
</head>
<body>
<div id="pagination"></div>
<div id="cards"></div>
</body>
</html>
const renderCards = async (pageSize, pageIndex) => {
$('#cards').empty();
const startIndex = (pageIndex - 1) * pageSize;
const endIndex = pageIndex * pageSize;
const hexSubset = hexCodes.slice(startIndex, endIndex);
const promises = hexSubset.map((hex) => fetchData(hex));
try {
const pageColors = await Promise.all(promises);
pageColors.forEach((color) => {
const image = $('<img>').attr({
src: color.image,
alt: color.name,
});
$('#cards').append(image);
});
} catch (error) {
console.error('Error rendering cards:', error);
}
};
$(() => {
const pagination = $('#pagination')
.dxPagination({
// ...
})
.dxPagination('instance');
const pageSize = pagination.option('pageSize');
const pageIndex = pagination.option('pageIndex');
renderCards(pageSize, pageIndex);
});
#cards {
display: flex;
justify-content: center;
flex-wrap: wrap;
}Angular
<dx-pagination ... ></dx-pagination>
<div id="cards">
<ng-container *ngFor="let color of visibleCards">
<img [src]="color.image" [alt]="color.name" />
</ng-container>
</div>
import { Component } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { ColorService, Color } from './app.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [ColorService],
})
export class AppComponent {
// ...
colors: Map<string, Color> = new Map();
visibleCards: Color[] = [];
constructor(private readonly colorService: ColorService) {}
ngOnInit(): void {
// ...
void this.fetchColorsForPage();
}
async fetchColorsForPage(): Promise<void> {
const startIndex = (this.pageIndex - 1) * this.pageSize;
const endIndex = this.pageIndex * this.pageSize;
const hexSubset = this.hexCodes.slice(startIndex, endIndex);
const promises: Promise<Color>[] = hexSubset.map((hex) => {
if (this.colors.has(hex)) {
return Promise.resolve(this.colors.get(hex)!);
} else {
return firstValueFrom(this.colorService.fetchColorData(hex)).then((data) => {
const colorData: Color = data;
this.colors.set(hex, colorData);
return colorData;
});
}
});
try {
const fetchedColors = await Promise.all(promises);
this.visibleCards = fetchedColors;
} catch (error) {
console.error('Error fetching colors:', error);
}
}
}
// ...
export interface Color {
image: string;
name: string;
}
#cards {
display: flex;
justify-content: center;
flex-wrap: wrap;
}Vue
<template>
<DxPagination ... />
<div id="cards">
<div v-for="color in visibleCards" :key="color.name">
<img :src="color.image" :alt="color.name" />
</div>
</div>
</template>
<script setup lang="ts">
// ...
interface Color {
image: string;
name: string;
}
const visibleCards = ref([] as Color[]);
const colorCache = new Map<string, Color>();
const fetchColorsForPage = async () => {
const startIndex = (pageIndex.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
const hexSubset = hexCodes.value.slice(startIndex, endIndex);
const promises = hexSubset.map((hex) => {
if (colorCache.has(hex)) {
return Promise.resolve(colorCache.get(hex));
}
return fetchColorData(hex).then((color) => {
if (color) {
colorCache.set(hex, color);
}
return color;
});
});
try {
const results = await Promise.all(promises);
visibleCards.value = results.filter((color): color is Color => color !== null);
} catch (error) {
console.error('Error fetching colors:', error);
}
};
onMounted(() => {
// ...
fetchColorsForPage();
});
</script>
<style>
#cards {
display: flex;
justify-content: center;
flex-wrap: wrap;
}
</style>React
import React, { useState, useRef, useEffect } from 'react';
// ...
interface Color {
image: string;
name: string;
}
function App(): JSX.Element {
// ...
const [visibleCards, setVisibleCards] = useState<Color[]>([]);
const colorsCache = useRef<Map<string, Color>>(new Map());
const fetchColorsForPage = useCallback(async (): Promise<void> => {
const startIndex = (pageIndex - 1) * pageSize;
const endIndex = startIndex + pageSize;
const hexSubset = hexCodes.current.slice(startIndex, endIndex);
const promises = hexSubset.map((hex) => {
if (colorsCache.current.has(hex)) {
return Promise.resolve(colorsCache.current.get(hex));
}
return fetchColorData(hex).then((color) => {
if (color) {
colorsCache.current.set(hex, color);
}
return color;
});
});
try {
const results = await Promise.all(promises);
const filteredColors = results.filter((color): color is Color => color !== null);
setVisibleCards(filteredColors);
} catch (error) {
console.error('Error fetching colors:', error);
}
}, [pageIndex, pageSize]);
useEffect(() => {
fetchColorsForPage().catch((error) => {
console.error('Error updating visible cards:', error);
});
}, [fetchColorsForPage]);
return (
<Pagination ... />
<div id="cards">
{visibleCards.map((color, index) => (
<div key={index}>
<img src={color.image} alt={color.name} />
</div>
))}
</div>
);
}
export default App;
#cards {
display: flex;
justify-content: center;
flex-wrap: wrap;
}Handle Page Size and Index Changes
The render function is called on every page index/size change:
jQuery
// ...
$(() => {
const pagination = $('#pagination')
.dxPagination({
// ...
onOptionChanged: (e) => {
if (e.name === 'pageSize' || e.name === 'pageIndex') {
const pageIndex = pagination.option('pageIndex');
const pageSize = pagination.option('pageSize');
renderCards(pageSize, pageIndex);
}
},
})
.dxPagination('instance');
// ...
});Angular
<dx-pagination ...
(pageIndexChange)="onPageIndexChange($event)"
(pageSizeChange)="onPageSizeChange($event)"
>
</dx-pagination>
<!-- ... -->
// ...
export class AppComponent {
// ...
onPageIndexChange(val: number): void {
this.pageIndex = val;
void this.fetchColorsForPage();
}
onPageSizeChange(val: number): void {
this.pageSize = val;
void this.fetchColorsForPage();
}
}Vue
<template>
<DxPagination ...
@update:page-index="onPageIndexChange"
@update:page-size="onPageSizeChange"
/>
<!-- ... -->
</template>
<script setup lang="ts">
// ...
const onPageIndexChange = (value: number) => {
pageIndex.value = value;
fetchColorsForPage();
};
const onPageSizeChange = (value: number) => {
pageSize.value = value;
fetchColorsForPage();
};
</script>React
import React, { useState, useRef, useEffect, useCallback } from 'react';
// ...
function App(): JSX.Element {
// ...
const onPageIndexChange = useCallback((value: number) => {
setPageIndex(value);
}, []);
const onPageSizeChange = useCallback((value: number) => {
setPageSize(value);
}, []);
return (
<Pagination ...
onPageIndexChange={onPageIndexChange}
onPageSizeChange={onPageSizeChange}
/>
<!-- ... -->
);
}
export default App;Integrate LoadPanel
The following code integrate a load panel into the application. The panel appears when the app requests card data from the remote service. This step is optional.
jQuery
To integrate the DevExtreme LoadPanel component:
<html>
<head>
<!-- ... -->
</head>
<body>
<div id="pagination"></div>
<div id="cards"></div>
<div id="load-panel"></div>
</body>
</html>
$(() => {
const loadPanel = $('#load-panel')
.dxLoadPanel({
position: {
my: 'top',
at: 'top',
of: '#cards',
},
visible: false,
showIndicator: true,
showPane: true,
hideOnOutsideClick: false,
})
.dxLoadPanel('instance');
const pagination = $('#pagination')
.dxPagination({
// ...
onOptionChanged: (e) => {
if (e.name === 'pageSize' || e.name === 'pageIndex') {
const pageIndex = pagination.option('pageIndex');
const pageSize = pagination.option('pageSize');
loadPanel.show();
renderCards(pageSize, pageIndex).finally(() => loadPanel.hide());
}
},
})
.dxPagination('instance');
// ...
loadPanel.show();
renderCards(pageSize, pageIndex).finally(() => loadPanel.hide());
});Angular
To integrate the DevExtreme LoadPanel component:
- Add a LoadPanel to the code.
- Display it before calling the render function.
- Hide it after render.
<dx-pagination ... ></dx-pagination>
<!-- ... -->
<dx-load-panel
[(visible)]="loadPanelVisible"
[showIndicator]="true"
[showPane]="true"
[hideOnOutsideClick]="false"
>
<dxo-position my="top" at="top" of="#cards"></dxo-position>
</dx-load-panel>
// ...
export class AppComponent {
// ...
loadPanelVisible = false;
async fetchColorsForPage(): Promise<void> {
const startIndex = (this.pageIndex - 1) * this.pageSize;
const endIndex = this.pageIndex * this.pageSize;
const hexSubset = this.hexCodes.slice(startIndex, endIndex);
const promises: Promise<Color>[] = hexSubset.map((hex) => {
// ...
});
this.loadPanelVisible = true;
try {
const fetchedColors = await Promise.all(promises);
this.visibleCards = fetchedColors;
} catch (error) {
console.error('Error fetching colors:', error);
} finally {
this.loadPanelVisible = false;
}
}
}Vue
To integrate the DevExtreme LoadPanel component:
- Add a LoadPanel to the code.
- Display it before calling the render function.
- Hide it after render.
<template>
<DxPagination ... />
<!-- ... -->
<DxLoadPanel
v-model:visible="loadPanelVisible"
:show-indicator="true"
:show-pane="true"
:hide-on-outside-click="false"
>
<DxPosition my="top" at="top" of="#cards" />
</DxLoadPanel>
</template>
<script setup lang="ts">
// ...
import { DxLoadPanel, DxPosition } from 'devextreme-vue/load-panel';
const loadPanelVisible = ref(false);
const fetchColorsForPage = async () => {
loadPanelVisible.value = true;
const startIndex = (pageIndex.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
const hexSubset = hexCodes.value.slice(startIndex, endIndex);
const promises = hexSubset.map((hex) => {
// ...
});
try {
const results = await Promise.all(promises);
visibleCards.value = results.filter((color): color is Color => color !== null);
} catch (error) {
console.error('Error fetching colors:', error);
} finally {
loadPanelVisible.value = false;
}
};
// ...
</script>React
To integrate the DevExtreme LoadPanel component:
- Add a LoadPanel to the code.
- Display it before calling the render function.
- Hide it after render.
// ...
import LoadPanel, { Position } from 'devextreme-react/load-panel';
function App(): JSX.Element {
// ...
const [loadPanelVisible, setLoadPanelVisible] = useState<boolean>(false);
const fetchColorsForPage = useCallback(async (): Promise<void> => {
setLoadPanelVisible(true);
const startIndex = (pageIndex - 1) * pageSize;
const endIndex = startIndex + pageSize;
const hexSubset = hexCodes.current.slice(startIndex, endIndex);
const promises = hexSubset.map((hex) => {
// ...
});
try {
const results = await Promise.all(promises);
const filteredColors = results.filter((color): color is Color => color !== null);
setVisibleCards(filteredColors);
} catch (error) {
console.error('Error fetching colors:', error);
} finally {
setLoadPanelVisible(false);
}
}, [pageIndex, pageSize]);
// ...
return (
<Pagination ... />
<!-- ... -->
<LoadPanel
visible={loadPanelVisible}
showIndicator={true}
showPane={true}
hideOnOutsideClick={false}
>
<Position my="top" at="top" of="#cards" />
</LoadPanel>
);
}
export default App;If you have technical questions, please create a support ticket in the DevExpress Support Center.