Add Pagination feature to a jhipster react project

Himanshu Pratap
4 min readSep 9, 2020

--

Jhipster provides a very powerful way to add pagination and filter functionality to all the entities , if they are being generated using jdl file.

Example jdl file

entity CarProduction{
prodDate LocalDate required,
asset String required,
model String required,
engine String,
prodCount Double
}
service all with serviceClass
paginate all with pagination
filter all

Just by line “paginate all with pagination” and “filter all”, all the entities get the functionality of pagination and filter respectively.

Usually, pagination is required when we are trying to access all the data of a particular entity. and want to display these data (thousands of rows) on the front-end, say using table component. If the table component supports lazy loading and server side pagination, you can get fast render of table.

However, due to pagination the maxium number of rows that gets returned is limited to 2000. When you add pagination to the code, it adds pagination to all the api calls, even to criteria based api calls that provided filter functionality. Now, if you want to get all the data based on filter criteria, you are limited to only 2000 rows at a time. So, never use pagination and filter both to the same entity. I prefer to add filter functionality and write the pagination code by self.

How to add pagination to the jhipster react code which was geneerated without pagination .

Steps:

Backend API addition.
Add REST API in the rest controller class, here we have created pagination api following pattern <entityname>s-page

@GetMapping("/car-productions-page")
public ResponseEntity<List<CarProduction>> getAllCarProductionsPage(Pageable pageable) {
log.debug("REST request to get a page of carProductions");
Page<CarProduction> page = carProductionService.findAllPage(pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
return ResponseEntity.ok().headers(headers).body(page.getContent());
}

Add method to the service class

@Transactional(readOnly = true)
public Page<CarProduction> findAllPage(Pageable pageable) {
log.debug("Request to get all Car Productions");
return carProductionRepository.findAll(pageable);
}

Frontend react code modification

Go to entity folder webapp/app/entities/ and modify following files.

  1. Edit file <entityname>.reducer.ts
    a. Modify initial state object, add attribute totalItems
const initialState = {
loading: false,
errorMessage: null,
entities: [] as ReadonlyArray<IMonthlyProductionAllocation>,
entity: defaultValue,
updating: false,
totalItems: 0,
updateSuccess: false,
};

b. Edit reducer case SUCESS FETCH_entityname_LIST. Add total items entry

case SUCCESS(ACTION_TYPES.FETCH_CARPRODUCTION_LIST):
return {
...state,
loading: false,
entities: action.payload.data,
totalItems: parseInt(action.payload.headers['x-total-count'], 10),
};

c. edit action getEntities

export const getEntities: ICrudGetAllAction<ICarProduction> = (page, size, sort) => {
const requestUrl = `${apiUrl}-page${sort ? `?page=${page}&size=${size}&sort=${sort}` : ''}`;
return {
type: ACTION_TYPES.FETCH_CARPRODUCTION_LIST,
payload: axios.get<ICarProduction>(`${requestUrl}${sort ? '&' : '?'}cacheBuster=${new Date().getTime()}`),
};
};

2. Edit file <entity name>.tsx as follows.

a. Replace

import { Translate, ICrudGetAllAction, TextFormat } from ‘react-jhipster’; Byimport { Translate, ICrudGetAllAction, TextFormat, getSortState, IPaginationBaseState, JhiPagination, JhiItemCount } from ‘react-jhipster’;

b. Add following import statement

import { ITEMS_PER_PAGE } from 'app/shared/util/pagination.constants';
import { overridePaginationStateWithQueryParams } from 'app/shared/util/entity-utils';

c. Add following lines

const [paginationState, setPaginationState] = useState(
overridePaginationStateWithQueryParams(getSortState(props.location, ITEMS_PER_PAGE), props.location.search)
);
const getAllEntities = () => {
props.getEntities(paginationState.activePage - 1, paginationState.itemsPerPage, `${paginationState.sort},${paginationState.order}`);
};
const sortEntities = () => {
getAllEntities();
const endURL = `?page=${paginationState.activePage}&sort=${paginationState.sort},${paginationState.order}`;
if (props.location.search !== endURL) {
props.history.push(`${props.location.pathname}${endURL}`);
}
};

d. Replace

useEffect(() => {
props.getEntities();
}, []);
ByuseEffect(() => {
sortEntities();
}, [paginationState.activePage, paginationState.order, paginationState.sort]);

e. add following codes right below above modified code.

useEffect(() => {
const params = new URLSearchParams(props.location.search);
const page = params.get('page');
const sort = params.get('sort');
if (page && sort) {
const sortSplit = sort.split(',');
setPaginationState({
...paginationState,
activePage: +page,
sort: sortSplit[0],
order: sortSplit[1],
});
}
}, [props.location.search]);
const sort = p => () => {
setPaginationState({
...paginationState,
order: paginationState.order === 'asc' ? 'desc' : 'asc',
sort: p,
});
};
const handlePagination = currentPage =>
setPaginationState({
...paginationState,
activePage: currentPage,
});

f. Replace

const { carProductionList, match, loading } = props;Byconst { carProductionList, match, loading, totalItems } = props;

g. Add className to all the table header ie th entry in syntax
className=”hand” onClick={sort(‘<column field>’)}
Eg.

<th className="hand" onClick={sort('id')}>
<th className="hand" onClick={sort('prodDate')}>

h. Modify edit and delete button ‘to’ link value.
Append below at end of ‘to’ value after removing backtick.
?page=${paginationState.activePage}&sort=${paginationState.sort},${paginationState.order}`
Eg. Replace

<Button 
tag={Link}
to={`${match.url}/${carProduction.id}/edit`}
By<Button
tag={Link}
to={`${match.url}/${carProduction.id}/edit?page=${paginationState.activePage}&sort=${paginationState.sort},${paginationState.order}`}

i. Look for code

!loading && (
<div className="alert alert-warning">
<Translate contentKey="myApp.carProduction.home.notFound">
No Car Production found
</Translate>
</div>
)
)}
</div>

and add below lines of code right after it.

{props.totalItems ? (
<div className={carProductionList && carProductionList.length > 0 ? '' : 'd-none'}>
<Row className="justify-content-center">
<JhiItemCount page={paginationState.activePage} total={totalItems} itemsPerPage={paginationState.itemsPerPage} i18nEnabled />
</Row>
<Row className="justify-content-center">
<JhiPagination
activePage={paginationState.activePage}
onSelect={handlePagination}
maxButtons={5}
itemsPerPage={paginationState.itemsPerPage}
totalItems={props.totalItems}
/>
</Row>
</div>
) : (
''
)}

j. Add totalItems attribute to map to stateprops
totalItems: <entityname>.totalItems,
Eg.

const mapStateToProps = ({ carProduction }: IRootState) => ({
carProductionList: carProduction.entities,
loading: carProduction.loading,
});
Byconst mapStateToProps = ({ carProduction }: IRootState) => ({
carProductionList: carProduction.entities,
loading: carProduction.loading,
totalItems: carProduction.totalItems,
});

3. Modify Test cases for the frontend
Edit file src/test/javascript/spec/app/entities/<entity-name>/<entity-name>-reducer-spec.ts

a. Add attribute totalItems to initial state.
eg.

const initialState = {
loading: false,
errorMessage: null,
entities: [] as ReadonlyArray<ICarProduction>, const payload = { data: [{ 1: 'fake1' }, { 2: 'fake2' }] };
entity: defaultValue,
totalItems: 0,
updating: false,
updateSuccess: false,
};

b. Modify payload under code describe(‘Successes’, () =>{ …

const payload = { data: [{ 1: 'fake1' }, { 2: 'fake2' }] };Byconst payload = { data: [{ 1: 'fake1' }, { 2: 'fake2' }], headers: { 'x-total-count': 123 } };

c. Modify toEqual attriutes under code describe(‘Successes’, () =>{ …

).toEqual({
...initialState,
loading: false,
entities: payload.data,
});
By).toEqual({
...initialState,
loading: false,
totalItems: payload.headers['x-total-count'],
entities: payload.data,
});
});

Thats it !

--

--

Himanshu Pratap
Himanshu Pratap

Written by Himanshu Pratap

System Administrator and Full stack web developer.

No responses yet