In this tutorial, I will show you how to build a full-stack Pagination (React + Spring Boot) example on Server side. The back-end server uses Spring Data and Spring Web for REST APIs, front-end side is a React App with Axios for HTTP Requests.
Related Posts:
– React + Spring Boot + MySQL: CRUD example
– React + Spring Boot + PostgreSQL: CRUD example
– React + Spring Boot + MongoDB: CRUD example
– React + Spring Boot: File Upload/Download example
– React + Spring Boot: JWT Authentication & Authorization example
Contents
Pagination with React & Spring Boot example
Assume that we have tutorials table in database like this:
We need to export APIs for pagination (with/without filter) as following samples:
/api/tutorials?page=1&size=3
/api/tutorials?size=5
: using default value for page/api/tutorials?title=data&page=1&size=5
: pagination & filter by title containing ‘data’/api/tutorials/published?page=2
: pagination & filter by ‘published’ status
This is structure of the result that we want to get from the APIs:
{
"totalItems": 32,
"tutorials": [...],
"totalPages": 3,
"currentPage": 1
}
Our React app will display the result with pagination:
You can change to a page with larger index:
Or change page size (quantity of items per page):
Or paging with filter:
If you want to make pagination with table, kindly visit:
React Table Pagination (Server side) with Search
Full-stack Architecture
We’re gonna build the Spring Boot + React Pagination application with following architecture:
– Spring Boot exports REST Apis using Spring Web MVC & interacts with Database using Spring Data.
– React Client sends HTTP Requests and retrieve HTTP Responses using axios, shows data on the components. We also use React Router for navigating to pages.
Spring Boot Server side Pagination
Overview
The Spring Boot Server will be built with Spring Web and Spring Data.
It can export API with endpoint that supports both pagination and filter:
api/tutorials?title=[keyword]&page=[index]&size=[number]
The response structure looks like this:
{
"totalItems": 32,
"tutorials": [
{
"id": 7,
"title": "bezkoder Tut#7",
"description": "Tut#6 Description",
"published": false
},
{
"id": 8,
"title": "bezkoder Tut#8",
"description": "Tut#7 Description",
"published": true
},
{
"id": 9,
"title": "bezkoder Tut#9",
"description": "Tut#8 Description",
"published": true
}
],
"totalPages": 11,
"currentPage": 2
}
Project Structure
This is the structure of the project that we’re gonna build:
Let me explain it briefly.
– Tutorial
data model class corresponds to entity/document and table/collection tutorials.
– TutorialRepository
is an interface for CRUD methods and custom finder methods. It will be autowired in TutorialController
.
– TutorialController
is a RestController which has request mapping methods for RESTful requests such as: getAllTutorials, createTutorial, updateTutorial, deleteTutorial, findByPublished…
– Configuration for Spring Datasource, DB Connection in application.properties.
– pom.xml contains dependencies for Spring Boot and MySQL/PostgreSQL/MongoDB.
Implementation
Step by step to make the Server side Pagination back-end for this React front-end can be found at one of following posts:
– For MySQL/PostgreSQL: Spring Boot Pagination & Filter example | Spring JPA, Pageable (with Github)
– For MongoDB: Spring Boot MongoDB Pagination example with Spring Data (with Github)
React Pagination Client
Overview
– The App
component is a container with React Router
. It has navbar
that links to routes paths.
– TutorialsList
component gets and displays Tutorials with pagination and filter.
– Tutorial
component has form for editing Tutorial’s details based on :id
.
– AddTutorial
component has form for submission new Tutorial.
– These Components call TutorialDataService
methods which use axios
to make HTTP requests and receive responses.
Project Structure
– package.json contains 4 main modules: react
, react-router-dom
, axios
& bootstrap
.
– App
is the container that has Router
& navbar.
– There are 3 components: TutorialsList
, Tutorial
, AddTutorial
.
– http-common.js initializes axios with HTTP base Url and headers.
– TutorialDataService
has methods for sending HTTP requests to the Apis.
– .env configures port for this React CRUD App.
Setup React.js Application
Open cmd at the folder you want to save Project folder, run command:
npx create-react-app react-pagination-material-ui
After the process is done. We create additional folders and files like the following tree:
public
src
components
add-tutorial.component.js
tutorial.component.js
tutorials-list.component.js
services
tutorial.service.js
App.css
App.js
index.js
package.json
You can follow step by step, or get source code in this post:
React Material UI examples with a CRUD Application
The React Project contains structure that we only need to add some changes (in tutorials-list.component.js and tutorial.service.js) to make the pagination work well.
Or you can get the new Github source code at the end of this tutorial.
Setup Material-UI for React Pagination App
We need to install both Material-UI core and lab with command:
npm install @material-ui/core @material-ui/lab
Initialize Axios for React HTTP Client
Let’s install axios with command: npm install axios
.
Under src folder, we create http-common.js file with following code:
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:8080/api",
headers: {
"Content-type": "application/json"
}
});
Create Data Service
In this step, we’re gonna create a service that uses axios
object above to send HTTP requests.
services/tutorial.service.js
import http from "../http-common";
class TutorialDataService {
getAll(params) {
return http.get("/tutorials", { params });
}
// other CRUD methods
}
export default new TutorialDataService();
In the code above, you can see that we pass params
object to GET method.
The params
object will have one, two or all fields: title
, page
, size
.
Create React Components with Pagination
This component has:
- a search bar for finding Tutorials by title.
- a select element for quantity of items per page.
- a Material-UI Pagination component
- a tutorials array displayed as a list on the left.
- a selected Tutorial which is shown on the right.
So we will have following state:
– search and display Tutorials:
searchTitle
tutorials
currentTutorial
andcurrentIndex
– pagination:
page
: current pagecount
: total pagespageSize
: number of items in each page
For pagination, we need to use TutorialDataService.getAll()
methods.
components/tutorials-list.component.js
import React, { Component } from "react";
import TutorialDataService from "../services/tutorial.service";
...
export default class TutorialsList extends Component {
constructor(props) {
super(props);
this.onChangeSearchTitle = this.onChangeSearchTitle.bind(this);
this.retrieveTutorials = this.retrieveTutorials.bind(this);
this.refreshList = this.refreshList.bind(this);
this.setActiveTutorial = this.setActiveTutorial.bind(this);
this.removeAllTutorials = this.removeAllTutorials.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
this.handlePageSizeChange = this.handlePageSizeChange.bind(this);
this.state = {
tutorials: [],
currentTutorial: null,
currentIndex: -1,
searchTitle: "",
page: 1,
count: 0,
pageSize: 3,
};
this.pageSizes = [3, 6, 9];
}
componentDidMount() {
this.retrieveTutorials();
}
onChangeSearchTitle(e) {
const searchTitle = e.target.value;
this.setState({
searchTitle: searchTitle,
});
}
getRequestParams(searchTitle, page, pageSize) {
let params = {};
if (searchTitle) {
params["title"] = searchTitle;
}
if (page) {
params["page"] = page - 1;
}
if (pageSize) {
params["size"] = pageSize;
}
return params;
}
retrieveTutorials() {
const { searchTitle, page, pageSize } = this.state;
const params = this.getRequestParams(searchTitle, page, pageSize);
TutorialDataService.getAll(params)
.then((response) => {
const { tutorials, totalPages } = response.data;
this.setState({
tutorials: tutorials,
count: totalPages,
});
console.log(response.data);
})
.catch((e) => {
console.log(e);
});
}
...
handlePageChange(event, value) {
this.setState(
{
page: value,
},
() => {
this.retrieveTutorials();
}
);
}
handlePageSizeChange(event) {
this.setState(
{
pageSize: event.target.value,
page: 1
},
() => {
this.retrieveTutorials();
}
);
}
render() {
...
}
}
Let me explain some lines of code.
In the retrieveTutorials()
method:
– We get searchTitle
, page
, pageSize
state and transform them into params
object:
{
"title": searchTitle,
"page": page - 1,
"size": pageSize
}
– We use tutorials
and totalPages
as count
state from the response data:
{
"totalItems": 8,
"tutorials": [...],
"totalPages": 3,
"currentPage": 1
}
handlePageChange()
and handlePageSizeChange()
methods are for setting new page
and pageSize
with callback invoking retrieveTutorials()
that updates the tutorials List when pagination information changes.
Let’s continue to implement render()
method:
...
import Pagination from "@material-ui/lab/Pagination";
export default class TutorialsList extends Component {
...
render() {
const {
searchTitle,
tutorials,
currentTutorial,
currentIndex,
page,
count,
pageSize,
} = this.state;
return (
<div className="list row">
<div className="col-md-8">
<div className="input-group mb-3">
<input
type="text"
className="form-control"
placeholder="Search by title"
value={searchTitle}
onChange={this.onChangeSearchTitle}
/>
<div className="input-group-append">
<button
className="btn btn-outline-secondary"
type="button"
onClick={this.retrieveTutorials}
>
Search
</button>
</div>
</div>
</div>
<div className="col-md-6">
<h4>Tutorials List</h4>
<div className="mt-3">
{"Items per Page: "}
<select onChange={this.handlePageSizeChange} value={pageSize}>
{this.pageSizes.map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
<Pagination
className="my-3"
count={count}
page={page}
siblingCount={1}
boundaryCount={1}
variant="outlined"
shape="rounded"
onChange={this.handlePageChange}
/>
</div>
<ul className="list-group">
{tutorials &&
tutorials.map((tutorial, index) => (
<li
className={
"list-group-item " +
(index === currentIndex ? "active" : "")
}
onClick={() => this.setActiveTutorial(tutorial, index)}
key={index}
>
{tutorial.title}
</li>
))}
</ul>
</div>
...
</div>
);
}
}
Configure Port for Web API
Because most of HTTP Server use CORS configuration that accepts resource sharing retricted to some sites or ports, so we also need to configure port for our App.
In project folder, create .env file with following content:
PORT=8081
Now we’ve set our app running at port 8081
.
Run React Pagination App
First you need to run the Server at one of following posts:
- Spring Boot Pagination & Filter example | Spring JPA, Pageable
- Spring Boot MongoDB Pagination example with Spring Data
Then you can run our App with command: npm start
.
If the process is successful, open Browser with Url: http://localhost:8081/
and check it.
React Spring Boot Pagination using Hooks
Step by step to build React Hooks App for the Server side pagination back-end above can be found at:
React Pagination using Hooks example (with Github)
Conclusion
Now we have an overview of full-stack React + Spring Boot Server side Pagination example.
We also take a look at client-server architecture for REST API using Spring Boot with Spring Data, as well as React project structure for building a front-end app to make HTTP requests and consume responses.
Next tutorials show you more details about how to implement the system:
– Back-end (with Github):
– Front-end (with Github):
You may want to know how to run both projects in one place:
How to integrate React.js with Spring Boot
Fullstack CRUD App:
– React + Spring Boot + MySQL: CRUD example
– React + Spring Boot + PostgreSQL: CRUD example
– React + Spring Boot + MongoDB: CRUD example
File Upload: React + Spring Boot: File Upload/Download example
Or Security: React + Spring Boot: JWT Authentication & Authorization example
Happy learning, see you again!
Thanks ! <3