Skip to main content

Server-Side Pagination with React-Table and Spring Boot JPA with H2 Database

Pagination is a common technique used to split large amounts of data into smaller, more manageable chunks. With server-side pagination, data is retrieved from the server in smaller batches, reducing the amount of data transferred over the network and improving application performance.

React-Table provides a wide range of built-in features such as sorting, filtering, pagination, row selection, and column resizing. These features can be easily configured and customized to fit specific requirements. For example, you can customize the sorting behavior to handle multiple sorting criteria, or you can add custom filters to the table to handle complex data filtering scenarios.

Additionally, React-Table provides a flexible API that allows developers to extend its functionality with custom hooks, plugins, and components. This means that you can easily add custom functionality to the table, such as exporting data to CSV or integrating with external data sources.

In terms of styling

React-Table provides a simple and flexible approach to styling tables using CSS. It provides a set of pre-defined CSS classes that can be used to style various parts of the table, such as headers, cells, and rows. You can also customize the CSS classes and styles to fit specific design requirements. Additionally, React-Table supports various styling libraries such as Bootstrap and Material-UI, making it easy to integrate with existing design systems.

Overall, the highly customizable functionality and styling options provided by React-Table make it a popular choice for building complex and flexible data tables in React applications.

Prerequisites

Before we begin, make sure you have the following software installed on your machine:

  • Java version: openjdk 17.0.6 2023-01-17 LTS
  • Node version: v16.19.1
  • NPM version: 8.19.3

You will also need a code editor of your choice, such as Visual Studio Code or IntelliJ IDEA.

Steps to Create a Spring Boot Project using https://start.spring.io

  1. Open your web browser and go to https://start.spring.io/.
  2. In the "Project Metadata" section, fill in the following details:
    • Group: com.basics-in-java.blogspot (or any other group name you prefer)
    • Artifact: Pagination
    • Name: Pagination
    • Description: Demo project for Spring Boot jpa Pagination using reactjs
    • Package name: com.basicsinjava.pagination (or any other package name you prefer)
  3. In the "Dependencies" section, search for the following dependencies and add them to your project:
    • Spring Web
    • Spring Data JPA
    • H2 Database
    • Commons IO
    • Spring Boot Starter Test
  4. After adding the dependencies, click on the "Generate" button to generate the project.
  5. Once the project is generated, extract the zip file to your preferred location.
  6. Importing the Project in IDE

If you are developing a Spring Boot application, you might have heard of the application.properties file. This file is used to configure various aspects of your Spring Boot application, such as the database connection, logging, and security. In this blog post, we will explore some of the properties that can be configured in the application.properties file.

spring.datasource.url

This property is used to specify the URL of the database that your Spring Boot application will connect to. In this example, we are using the H2 database with the URL jdbc:h2:~/test. The ~ character in the URL refers to the user's home directory.

spring.datasource.driverClassName

This property specifies the fully qualified name of the JDBC driver class that your Spring Boot application will use to connect to the database. In this example, we are using the H2 database with the driver class org.h2.Driver.

spring.jpa.database-platform

This property specifies the database dialect that your Spring Boot application will use. In this example, we are using the H2 dialect with the value org.hibernate.dialect.H2Dialect.

spring.h2.console.enabled

This property enables the H2 console in your Spring Boot application. The H2 console is a web-based database console that allows you to interact with your database. In this example, we have set the value to true to enable the console.

spring.h2.console.path

This property specifies the path where the H2 console will be available in your Spring Boot application. In this example, we have set the path to /h2-console.

spring.datasource.username

This property specifies the username that your Spring Boot application will use to connect to the database. In this example, we have set the username to sa.

spring.datasource.password

This property specifies the password that your Spring Boot application will use to connect to the database. In this example, we have left the password empty.

spring.datasource.initialization-mode

This property specifies how your Spring Boot application will initialize the database. In this example, we have set the value to always, which means that the database will be recreated every time the application starts.

spring.jpa.hibernate.ddl-auto

This property specifies the DDL mode that your Spring Boot application will use. In this example, we have set the value to create, which means that Hibernate will create the database schema on startup.

spring.jpa.show-sql

This property enables the SQL logging for your Spring Boot application. In this example, we have set the value to true to enable SQL logging.

spring.jpa.properties.hibernate.format_sql

This property specifies whether to format the SQL output. In this example, we have set the value to true to format the SQL output.


spring.datasource.url=jdbc:h2:~/test
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

load user data in to db

import com.basicsinjava.blogspot.Pagination.entity.User;
import com.basicsinjava.blogspot.Pagination.repository.UserRepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@Component
public class DataLoader {

    @Autowired
    private UserRepository userRepository;

    @PostConstruct
    public void loadData() throws IOException {
        URL url = new URL("https://randomuser.me/api/?results=500");
        String json = IOUtils.toString(url, Charset.forName("UTF-8"));
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(json);
        List<JsonNode> results = StreamSupport.stream(rootNode.get("results").spliterator(), false)
                .collect(Collectors.toList());

        List<User> users = results.stream().map(node -> {
            User user = new User();
            user.setFirstName(node.get("name").get("first").asText());
            user.setLastName(node.get("name").get("last").asText());
            user.setGender(node.get("gender").asText());
            user.setAge(node.get("dob").get("age").asInt());
            user.setEmail(node.get("email").asText());
            user.setPictureUrl(node.get("picture").get("large").asText());
            return user;
        }).collect(Collectors.toList());
        userRepository.saveAll(users);
    }
}

This Java class defines a Spring component named DataLoader that is responsible for loading data into the User repository. It uses the Apache Commons IO library and the Jackson library to parse a JSON response from a REST API and convert it into User objects. The class is annotated with @Component to indicate that it is a Spring component, and the UserRepository is injected into the class using @Autowired. The loadData() method is annotated with @PostConstruct, which means that it will be called by the Spring container after the bean has been initialized.

User entity should be:


  package com.basicsinjava.blogspot.Pagination.entity;
import jakarta.persistence.*;


@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    private String gender;

    private Integer age;

    private String email;

    private String pictureUrl;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPictureUrl() {
        return pictureUrl;
    }

    public void setPictureUrl(String pictureUrl) {
        this.pictureUrl = pictureUrl;
    }
}
  

repository should be:


package com.basicsinjava.blogspot.Pagination.repository;

import com.basicsinjava.blogspot.Pagination.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findAll(Pageable pageable);
}

the controller should be:


package com.basicsinjava.blogspot.Pagination.controller;

import com.basicsinjava.blogspot.Pagination.entity.User;
import com.basicsinjava.blogspot.Pagination.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping
    public Page<UserLong> getUsers(@PageableDefault(size = 10) Pageable pageable) {
        return userRepository.findAll(pageable);
    }
}

The react ui component should be look like this:



import React, { useState, useEffect } from "react";
import axios from "axios";
import { useTable, usePagination } from "react-table";

function UserTable() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [pageCount, setPageCount] = useState(0);

const fetchData = async ({ pageIndex, pageSize }) => {
setLoading(true);
try {
const response = await axios.get("http://localhost:8080/api/users", {
params: {
page: pageIndex,
size: pageSize,
},
});

const { content: jsonData, totalPages: newPageCount } = response.data;
if (Array.isArray(jsonData)) {
setData(jsonData);
setPageCount(newPageCount);
} else {
throw new Error("Data is not an array");
}
} catch (error) {
setError(error.message);
} finally {
setLoading(false);
}
};

const columns = React.useMemo(
() => [
{
Header: "Avatar",
accessor: "pictureUrl",
Cell: ({ value }) => (
<img src={value} alt="avatar" style={{ height: "50px" }} />
),
},
{ Header: "First Name", accessor: "firstName" },
{ Header: "Last Name", accessor: "lastName" },
{ Header: "Gender", accessor: "gender" },
{ Header: "Age", accessor: "age" },
{ Header: "Email", accessor: "email" },
],
[]
);
const tableInstance = useTable(
{ columns, data, manualPagination: true, pageCount },
usePagination
);

const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
gotoPage,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
setPageSize,
prepareRow,
state: { pageIndex, pageSize },
} = tableInstance;

useEffect(() => {
fetchData({ pageIndex, pageSize });
}, [pageIndex, pageSize]);

if (error) {
return <div>Error: {error}</div>;
}

return (
<>
<table {...getTableProps()} className="table table-striped">
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
))}
</tr>
);
})}
</tbody>
</table>

<div
className="pagination mt-3"
style={{ display: "flex", alignItems: "center" }}
>
<button
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
style={{ marginRight: "10px" }}
>
{"<<"}
</button>
<button
onClick={() => previousPage()}
disabled={!canPreviousPage}
style={{ marginRight: "10px" }}
>
{"<"}
</button>
<button
onClick={() => nextPage()}
disabled={!canNextPage}
style={{ marginRight: "10px" }}
>
{">"}
</button>
<button
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
style={{ marginRight: "10px" }}
>
{">>"}
</button>
<span>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span style={{ marginLeft: "10px" }}>
| Go to page:{" "}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={async (e) => {
const pageNumber = e.target.value
? Number(e.target.value) - 1
: 0;
console.log("pageNumber", pageNumber);
gotoPage(pageNumber);
}}
style={{ width: "50px", marginLeft: "5px" }}
/>
</span>{" "}
<select
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
style={{ marginLeft: "10px" }}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</>
);
}

export default UserTable;






  • The component imports the necessary dependencies, including React, useState, useEffect, axios, and react-table.
  • Inside the component function, the component defines several state variables using the useState hook:
    • data: An empty array to hold the user data fetched from the API endpoint
    • loading: A boolean value that indicates whether the component is currently fetching data
    • error: A variable to hold any errors that occur during the data fetch process

    • pageCount: The total number of pages of user data available from the API endpoint
  • The component defines a function called fetchData that uses the axios library to fetch data from the API endpoint. This function sets the loading state to true, makes an API call to retrieve data based on the page and pageSize provided as parameters, and updates the state variables accordingly.

  • The component defines a constant called columns that defines the column headers and accessor functions for each piece of user data. This is used to render the table.
  • The component uses the useTable and usePagination hooks from react-table to create an instance of the table with the specified columns and data. The manualPagination flag is set to true to enable manual pagination control.

  • The component uses destructuring to extract various properties and functions from the tableInstance object returned by useTable. These include properties like page, pageIndex, and pageSize, and functions like nextPage and previousPage that are used to control pagination.

  • The component uses the useEffect hook to call the fetchData function whenever the pageIndex or pageSize state variables change. This fetches new data from the API endpoint and updates the state variables accordingly.

  • The component renders the table and pagination controls using JSX, using the various properties and functions extracted from the tableInstance object. The table is displayed with the column headers and user data rows, and pagination controls are displayed at the bottom of the table to allow the user to navigate between pages of data.

Overall, this component demonstrates the use of several key React hooks and libraries to fetch and display data from an API endpoint, and provide pagination controls for the user.


source code : https://github.com/sudeepcv/pagination-using-react-table-and-spring-boot


Comments