Skip to content

Commit 6c0d88c

Browse files
committed
refactor: add reusable useTablePagination hook with page clamping
Signed-off-by: Vladimir Belousov <vbelouso@redhat.com>
1 parent f9e360e commit 6c0d88c

File tree

7 files changed

+205
-116
lines changed

7 files changed

+205
-116
lines changed

src/app/Accounts/components/AccountsTable.tsx

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,40 @@
11
import { ThProps, Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
2-
import React, { useState } from 'react';
2+
import React, { useState, useMemo } from 'react';
33
import { Link } from 'react-router-dom';
44
import { AccountResponseApi, ProviderApi } from '@api';
55
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
66
import { TablePagination } from '@app/components/common/TablesPagination';
7-
import { searchItems, filterByProvider, sortItems, paginateItems } from '@app/utils/tableFilters';
7+
import { searchItems, filterByProvider, sortItems } from '@app/utils/tableFilters';
88
import { useAccounts } from '@app/hooks/useAccounts';
9+
import { useTablePagination } from '@app/hooks/useTablePagination';
910

1011
export const AccountsTable: React.FunctionComponent<{
1112
searchValue: string;
1213
providerSelections: ProviderApi[] | null;
1314
}> = ({ searchValue, providerSelections }) => {
14-
const [page, setPage] = useState(1);
15-
const [perPage, setPerPage] = useState(20);
1615
const { data: allAccounts = [], isLoading } = useAccounts();
1716

1817
const [activeSortIndex, setActiveSortIndex] = useState<number | undefined>(0);
1918
const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc'>('asc');
2019

21-
let filtered = allAccounts;
22-
filtered = searchItems(filtered, searchValue, ['accountName']);
23-
filtered = filterByProvider(filtered, providerSelections);
20+
const filtered = useMemo(() => {
21+
let result = allAccounts;
22+
result = searchItems(result, searchValue, ['accountName']);
23+
result = filterByProvider(result, providerSelections);
2424

25-
if (activeSortIndex !== undefined && activeSortDirection) {
26-
const sortFields: (keyof AccountResponseApi)[] = ['accountName', 'provider', 'clusterCount'];
27-
filtered = sortItems(filtered, sortFields[activeSortIndex], activeSortDirection);
28-
}
25+
if (activeSortIndex !== undefined && activeSortDirection) {
26+
const sortFields: (keyof AccountResponseApi)[] = ['accountName', 'provider', 'clusterCount'];
27+
result = sortItems(result, sortFields[activeSortIndex], activeSortDirection);
28+
}
2929

30-
const paginated = paginateItems(filtered, page, perPage);
30+
return result;
31+
}, [allAccounts, searchValue, providerSelections, activeSortIndex, activeSortDirection]);
32+
33+
const { page, perPage, setPage, setPerPage, paginatedData, totalItems } = useTablePagination({
34+
data: filtered,
35+
initialPerPage: 20,
36+
filterDeps: [searchValue, providerSelections],
37+
});
3138

3239
const getSortParams = (columnIndex: number): ThProps['sort'] => ({
3340
sortBy: {
@@ -62,7 +69,7 @@ export const AccountsTable: React.FunctionComponent<{
6269
</Tr>
6370
</Thead>
6471
<Tbody>
65-
{paginated.map(account => (
72+
{paginatedData.map(account => (
6673
<Tr key={account.accountId}>
6774
<Td dataLabel={columnNames.name}>
6875
<Link to={`/accounts/${account.accountId}`}>{account.accountName}</Link>
@@ -75,7 +82,7 @@ export const AccountsTable: React.FunctionComponent<{
7582
</Table>
7683
)}
7784
<TablePagination
78-
itemCount={filtered.length}
85+
itemCount={totalItems}
7986
page={page}
8087
perPage={perPage}
8188
onSetPage={setPage}

src/app/Actions/AuditLogs/AuditLogsTable.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { getResultIcon, renderOperationLabel } from '@app/utils/renderUtils';
77
import { useTableSort } from '@app/hooks/useTableSort.tsx';
88
import { EmptyState } from '@patternfly/react-core';
99
import { TablePagination } from '@app/components/common/TablesPagination';
10-
import { paginateItems } from '@app/utils/tableFilters';
1110
import { SearchIcon } from '@patternfly/react-icons';
1211
import { AuditLogsTableProps } from './types';
1312
import { Link } from 'react-router-dom';
1413
import { useEvents } from '@app/hooks/useEvents';
14+
import { useTablePagination } from '@app/hooks/useTablePagination';
1515

1616
const columnNames = {
1717
action: 'Action',
@@ -35,39 +35,42 @@ export const AuditLogsTable: React.FunctionComponent<AuditLogsTableProps> = ({
3535
result,
3636
triggered_by,
3737
}) => {
38-
const [page, setPage] = React.useState(1);
39-
const [perPage, setPerPage] = React.useState(10);
40-
4138
const { data: allEvents = [], isLoading } = useEvents();
4239

43-
const filteredData = useMemo(() => {
44-
let filtered = allEvents;
40+
const filtered = useMemo(() => {
41+
let filteredResult = allEvents;
4542

4643
if (accountName) {
47-
filtered = filtered.filter(event => event.accountId?.toLowerCase().includes(accountName.toLowerCase()));
44+
filteredResult = filteredResult.filter(event =>
45+
event.accountId?.toLowerCase().includes(accountName.toLowerCase())
46+
);
4847
}
4948

5049
if (action?.length) {
51-
filtered = filtered.filter(event => action.includes(event.action as ActionOperations));
50+
filteredResult = filteredResult.filter(event => action.includes(event.action as ActionOperations));
5251
}
5352

5453
if (provider?.length) {
55-
filtered = filtered.filter(event => event.provider && provider.some(p => p === event.provider));
54+
filteredResult = filteredResult.filter(event => event.provider && provider.some(p => p === event.provider));
5655
}
5756

5857
if (result?.length) {
59-
filtered = filtered.filter(event => result.includes(event.result as ResultStatus));
58+
filteredResult = filteredResult.filter(event => result.includes(event.result as ResultStatus));
6059
}
6160

6261
if (triggered_by) {
63-
filtered = filtered.filter(event => event.triggeredBy?.toLowerCase().includes(triggered_by.toLowerCase()));
62+
filteredResult = filteredResult.filter(event =>
63+
event.triggeredBy?.toLowerCase().includes(triggered_by.toLowerCase())
64+
);
6465
}
6566

66-
return {
67-
count: filtered.length,
68-
items: paginateItems(filtered, page, perPage),
69-
};
70-
}, [allEvents, accountName, action, provider, result, triggered_by, page, perPage]);
67+
return filteredResult;
68+
}, [allEvents, accountName, action, provider, result, triggered_by]);
69+
70+
const { page, perPage, setPage, setPerPage, paginatedData, totalItems } = useTablePagination({
71+
data: filtered,
72+
filterDeps: [accountName, action, provider, result, triggered_by],
73+
});
7174

7275
const getSortableRowValues = (event: SystemEventResponseApi): (string | number | null)[] => {
7376
const { action, result, resourceId, accountId, provider, triggeredBy, description, timestamp } = event;
@@ -84,14 +87,14 @@ export const AuditLogsTable: React.FunctionComponent<AuditLogsTableProps> = ({
8487
};
8588

8689
const { sortedData, getSortParams } = useTableSort<SystemEventResponseApi>(
87-
filteredData.items,
90+
paginatedData,
8891
getSortableRowValues,
8992
7,
9093
'desc'
9194
);
9295

9396
if (isLoading) return <LoadingSpinner />;
94-
if (filteredData.count === 0) return <EmptyStateNoFound />;
97+
if (totalItems === 0) return <EmptyStateNoFound />;
9598

9699
return (
97100
<React.Fragment>
@@ -138,7 +141,7 @@ export const AuditLogsTable: React.FunctionComponent<AuditLogsTableProps> = ({
138141
</Tbody>
139142
</Table>
140143
<TablePagination
141-
itemCount={filteredData.count}
144+
itemCount={totalItems}
142145
page={page}
143146
perPage={perPage}
144147
onSetPage={setPage}

src/app/Actions/Scheduler/components/ActionsTable.tsx

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { renderActionTypeLabel, renderOperationLabel, renderActionStatusLabel } from '@app/utils/renderUtils';
22
import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
33
import { Label } from '@patternfly/react-core';
4-
import React, { useState, useEffect, useMemo } from 'react';
4+
import React, { useEffect, useMemo } from 'react';
55
import { ActionStatus, ActionOperations, ActionTypes } from '@app/types/types';
66
import { Link } from 'react-router-dom';
77
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
88
import { TablePagination } from '@app/components/common/TablesPagination';
9-
import { paginateItems } from '@app/utils/tableFilters';
109
import { ActionsColumn } from '@patternfly/react-table';
1110
import { rowActions } from './ActionsKebabMenu';
1211
import { useScheduleActions, useInvalidateScheduleActions } from '@app/hooks/useScheduleActions';
12+
import { useTablePagination } from '@app/hooks/useTablePagination';
1313

1414
export const ScheduleActionsTable: React.FunctionComponent<{
1515
actionType: ActionTypes | null;
@@ -19,9 +19,6 @@ export const ScheduleActionsTable: React.FunctionComponent<{
1919
accountId: string | null;
2020
reloadFlag: number;
2121
}> = ({ actionType, actionOperation, actionStatus, actionEnabled, accountId, reloadFlag }) => {
22-
const [page, setPage] = useState(1);
23-
const [perPage, setPerPage] = useState(10);
24-
2522
const { data: allActions = [], isLoading, refetch } = useScheduleActions();
2623
const invalidateScheduleActions = useInvalidateScheduleActions();
2724

@@ -31,36 +28,38 @@ export const ScheduleActionsTable: React.FunctionComponent<{
3128
}
3229
}, [reloadFlag, refetch]);
3330

34-
const filteredData = useMemo(() => {
35-
let filtered = allActions;
31+
const filtered = useMemo(() => {
32+
let result = allActions;
3633

3734
if (actionType) {
38-
filtered = filtered.filter(item => item.type === actionType);
35+
result = result.filter(item => item.type === actionType);
3936
}
4037

4138
if (accountId) {
42-
filtered = filtered.filter(item => item.accountId?.includes(accountId));
39+
result = result.filter(item => item.accountId?.includes(accountId));
4340
}
4441

4542
if (actionOperation?.length) {
46-
filtered = filtered.filter(item => {
43+
result = result.filter(item => {
4744
return actionOperation.includes(item.operation as never);
4845
});
4946
}
5047

5148
if (actionStatus) {
52-
filtered = filtered.filter(item => item.status === actionStatus);
49+
result = result.filter(item => item.status === actionStatus);
5350
}
5451

5552
if (actionEnabled !== null) {
56-
filtered = filtered.filter(item => item.enabled === actionEnabled);
53+
result = result.filter(item => item.enabled === actionEnabled);
5754
}
5855

59-
return {
60-
count: filtered.length,
61-
items: paginateItems(filtered, page, perPage),
62-
};
63-
}, [allActions, actionType, accountId, actionOperation, actionStatus, actionEnabled, page, perPage]);
56+
return result;
57+
}, [allActions, actionType, accountId, actionOperation, actionStatus, actionEnabled]);
58+
59+
const { page, perPage, setPage, setPerPage, paginatedData, totalItems } = useTablePagination({
60+
data: filtered,
61+
filterDeps: [actionType, actionOperation, actionStatus, actionEnabled, accountId],
62+
});
6463

6564
const columnNames = {
6665
id: 'ID',
@@ -96,7 +95,7 @@ export const ScheduleActionsTable: React.FunctionComponent<{
9695
</Tr>
9796
</Thead>
9897
<Tbody>
99-
{filteredData.items.map(action => (
98+
{paginatedData.map(action => (
10099
<Tr key={action.id}>
101100
<Td dataLabel={columnNames.id}>{action.id}</Td>
102101
<Td dataLabel={columnNames.type}>{renderActionTypeLabel(action.type)}</Td>
@@ -125,7 +124,7 @@ export const ScheduleActionsTable: React.FunctionComponent<{
125124
</Table>
126125
)}
127126
<TablePagination
128-
itemCount={filteredData.count}
127+
itemCount={totalItems}
129128
page={page}
130129
perPage={perPage}
131130
onSetPage={setPage}

src/app/Clusters/components/ClustersTable.tsx

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { renderStatusLabel } from '@app/utils/renderUtils';
22
import { ThProps, Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
3-
import React, { useState } from 'react';
3+
import React, { useState, useMemo } from 'react';
44
import { Link } from 'react-router-dom';
55
import { ClusterResponseApi } from '@api';
66
import { ClustersTableProps } from '../types';
77
import { LoadingSpinner } from '@app/components/common/LoadingSpinner';
88
import { TablePagination } from '@app/components/common/TablesPagination';
9-
import { searchItems, filterByStatus, filterByProvider, sortItems, paginateItems } from '@app/utils/tableFilters';
9+
import { searchItems, filterByStatus, filterByProvider, sortItems } from '@app/utils/tableFilters';
1010
import { EmptyState, EmptyStateVariant, EmptyStateBody, Title } from '@patternfly/react-core';
1111
import { CubesIcon } from '@patternfly/react-icons';
1212
import { useClusters } from '@app/hooks/useClusters';
13+
import { useTablePagination } from '@app/hooks/useTablePagination';
1314

1415
export const ClustersTable: React.FunctionComponent<ClustersTableProps> = ({
1516
clusterNameSearch,
@@ -18,45 +19,59 @@ export const ClustersTable: React.FunctionComponent<ClustersTableProps> = ({
1819
providerSelections,
1920
showTerminated,
2021
}) => {
21-
const [page, setPage] = useState(1);
22-
const [perPage, setPerPage] = useState(10);
2322
const { data: allClusters = [], isLoading } = useClusters();
2423

2524
const [activeSortIndex, setActiveSortIndex] = useState<number | undefined>(0);
2625
const [activeSortDirection, setActiveSortDirection] = useState<'asc' | 'desc'>('asc');
2726

28-
let processed = allClusters;
27+
const filtered = useMemo(() => {
28+
let processed = allClusters;
2929

30-
if (!showTerminated) {
31-
processed = processed.filter(cluster => cluster.status !== 'Terminated');
32-
}
30+
if (!showTerminated) {
31+
processed = processed.filter(cluster => cluster.status !== 'Terminated');
32+
}
3333

34-
if (clusterNameSearch) {
35-
processed = searchItems(processed, clusterNameSearch, ['clusterName']);
36-
}
34+
if (clusterNameSearch) {
35+
processed = searchItems(processed, clusterNameSearch, ['clusterName']);
36+
}
3737

38-
if (accountNameSearch) {
39-
processed = searchItems(processed, accountNameSearch, ['accountName']);
40-
}
38+
if (accountNameSearch) {
39+
processed = searchItems(processed, accountNameSearch, ['accountName']);
40+
}
4141

42-
processed = filterByStatus(processed, statusFilter);
43-
processed = filterByProvider(processed, providerSelections);
44-
45-
if (activeSortIndex !== undefined && activeSortDirection) {
46-
const sortFields: (keyof ClusterResponseApi)[] = [
47-
'clusterId',
48-
'clusterName',
49-
'status',
50-
'accountId',
51-
'provider',
52-
'region',
53-
'instanceCount',
54-
'consoleLink',
55-
];
56-
processed = sortItems(processed, sortFields[activeSortIndex], activeSortDirection);
57-
}
42+
processed = filterByStatus(processed, statusFilter);
43+
processed = filterByProvider(processed, providerSelections);
5844

59-
const paginated = paginateItems(processed, page, perPage);
45+
if (activeSortIndex !== undefined && activeSortDirection) {
46+
const sortFields: (keyof ClusterResponseApi)[] = [
47+
'clusterId',
48+
'clusterName',
49+
'status',
50+
'accountId',
51+
'provider',
52+
'region',
53+
'instanceCount',
54+
'consoleLink',
55+
];
56+
processed = sortItems(processed, sortFields[activeSortIndex], activeSortDirection);
57+
}
58+
59+
return processed;
60+
}, [
61+
allClusters,
62+
showTerminated,
63+
clusterNameSearch,
64+
accountNameSearch,
65+
statusFilter,
66+
providerSelections,
67+
activeSortIndex,
68+
activeSortDirection,
69+
]);
70+
71+
const { page, perPage, setPage, setPerPage, paginatedData, totalItems } = useTablePagination({
72+
data: filtered,
73+
filterDeps: [clusterNameSearch, accountNameSearch, statusFilter, providerSelections, showTerminated],
74+
});
6075

6176
const columnNames = {
6277
id: 'ID',
@@ -86,7 +101,7 @@ export const ClustersTable: React.FunctionComponent<ClustersTableProps> = ({
86101
return <LoadingSpinner />;
87102
}
88103

89-
if (processed.length === 0) {
104+
if (filtered.length === 0) {
90105
return (
91106
<EmptyState
92107
titleText={
@@ -128,7 +143,7 @@ export const ClustersTable: React.FunctionComponent<ClustersTableProps> = ({
128143
</Tr>
129144
</Thead>
130145
<Tbody>
131-
{paginated.map(cluster => (
146+
{paginatedData.map(cluster => (
132147
<Tr key={cluster.clusterId}>
133148
<Td dataLabel={columnNames.id}>
134149
<Link to={`/clusters/${cluster.clusterId}`}>{cluster.clusterId}</Link>
@@ -151,7 +166,7 @@ export const ClustersTable: React.FunctionComponent<ClustersTableProps> = ({
151166
</Tbody>
152167
</Table>
153168
<TablePagination
154-
itemCount={processed.length}
169+
itemCount={totalItems}
155170
page={page}
156171
perPage={perPage}
157172
onSetPage={setPage}

0 commit comments

Comments
 (0)