Skip to content

Commit 96e43fa

Browse files
committed
feat(Projects): Paginate with pages and context
This now has a context that each component under the provider can use. With this we reduce the code we need to write to handle state handling between components. Infinite pagination has been replaced with per-page pagination which should suit people better than infinitely loading. feat: proper pagination
1 parent c4dc93d commit 96e43fa

File tree

6 files changed

+108
-35
lines changed

6 files changed

+108
-35
lines changed

frontend/src/components/projects/Projects.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33

44
import { Content, PageGroup, PageSection } from "@patternfly/react-core";
55

6+
import { useState } from "react";
7+
import { PackitPagination } from "../shared/PackitPagination";
8+
import { PackitPaginationContext } from "../shared/PackitPaginationContext";
69
import { ProjectSearch } from "./ProjectSearch";
710
import { ProjectsList } from "./ProjectsList";
811

912
const Projects = () => {
13+
const [page, setPage] = useState(1);
14+
const [perPage, setPerPage] = useState(10);
15+
const value = { page, setPage, perPage, setPerPage };
16+
1017
return (
11-
<>
18+
<PackitPaginationContext.Provider value={value}>
1219
<PageSection hasBodyWrapper={false}>
1320
<Content>
1421
<Content component="h1">Projects</Content>
@@ -20,10 +27,11 @@ const Projects = () => {
2027
<PageGroup>
2128
<PageSection hasBodyWrapper={false}>
2229
<ProjectSearch />
30+
<PackitPagination />
2331
<ProjectsList />
2432
</PageSection>
2533
</PageGroup>
26-
</>
34+
</PackitPaginationContext.Provider>
2735
);
2836
};
2937

frontend/src/components/projects/ProjectsList.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Contributors to the Packit project.
22
// SPDX-License-Identifier: MIT
33

4-
import React, { useMemo } from "react";
4+
import React, { useContext, useMemo, useState } from "react";
55

66
import {
77
BlueprintIcon,
@@ -19,11 +19,13 @@ import {
1919
Thead,
2020
Tr,
2121
} from "@patternfly/react-table";
22-
import { useInfiniteQuery } from "@tanstack/react-query";
22+
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
2323
import { Link } from "@tanstack/react-router";
2424
import { Project } from "../../apiDefinitions";
2525
import { projectsQueryOptions } from "../../queries/project/projectsQuery";
2626
import { LoadMore } from "../shared/LoadMore";
27+
import { PackitPagination } from "../shared/PackitPagination";
28+
import { PackitPaginationContext } from "../shared/PackitPaginationContext";
2729

2830
function getProjectInfoURL(project: Project) {
2931
const urlArray = project.project_url?.split("/");
@@ -51,8 +53,9 @@ const columnNames = {
5153
type ColumnKey = keyof typeof columnNames;
5254

5355
const ProjectsList: React.FC<ProjectsListProps> = ({ forge, namespace }) => {
54-
const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } =
55-
useInfiniteQuery(projectsQueryOptions(forge, namespace));
56+
const { page, perPage } = useContext(PackitPaginationContext);
57+
const queryOptions = projectsQueryOptions(page, perPage, forge, namespace);
58+
const { data, isLoading } = useQuery(queryOptions);
5659
const expandedCells: Record<string, ColumnKey> = {};
5760

5861
// Headings
@@ -65,9 +68,6 @@ const ProjectsList: React.FC<ProjectsListProps> = ({ forge, namespace }) => {
6568
externalProjectLink: "External Project Link",
6669
};
6770

68-
// Create a memoization of all the data when we flatten it out. Ideally one should render all the pages separately so that rendering will be done faster
69-
const rows = useMemo(() => (data ? data.pages.flat() : []), [data]);
70-
7171
const TableHeads = [
7272
<Th width={25} key={columnNames.repositories}>
7373
{columnNames.repositories}
@@ -101,7 +101,7 @@ const ProjectsList: React.FC<ProjectsListProps> = ({ forge, namespace }) => {
101101
<Thead>
102102
<Tr>{TableHeads}</Tr>
103103
</Thead>
104-
{rows.map((project) => {
104+
{data?.map((project) => {
105105
const expandedCellKey = expandedCells
106106
? expandedCells[project.repo_name]
107107
: null;
@@ -177,11 +177,6 @@ const ProjectsList: React.FC<ProjectsListProps> = ({ forge, namespace }) => {
177177
);
178178
})}
179179
</Table>
180-
<LoadMore
181-
isFetchingNextPage={isFetchingNextPage}
182-
hasNextPage={hasNextPage}
183-
fetchNextPage={() => void fetchNextPage()}
184-
/>
185180
</>
186181
);
187182
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright Contributors to the Packit project.
2+
// SPDX-License-Identifier: MIT
3+
4+
import { Pagination } from "@patternfly/react-core";
5+
import React, { useContext } from "react";
6+
import { PackitPaginationContext } from "./PackitPaginationContext";
7+
8+
interface PackitPaginationProps {
9+
itemCount?: number;
10+
}
11+
12+
export const PackitPagination: React.FC<PackitPaginationProps> = ({
13+
itemCount,
14+
}) => {
15+
const { page, perPage, setPage, setPerPage } = useContext(
16+
PackitPaginationContext,
17+
);
18+
19+
const onSetPage = (
20+
_event: React.MouseEvent | React.KeyboardEvent | MouseEvent,
21+
newPage: number,
22+
) => {
23+
setPage(newPage);
24+
};
25+
26+
const onPerPageSelect = (
27+
_event: React.MouseEvent | React.KeyboardEvent | MouseEvent,
28+
newPerPage: number,
29+
newPage: number,
30+
) => {
31+
setPerPage(newPerPage);
32+
setPage(newPage);
33+
};
34+
35+
return (
36+
<Pagination
37+
isCompact
38+
toggleTemplate={({ firstIndex, lastIndex, itemCount }) => (
39+
<>
40+
<b>
41+
{firstIndex} - {lastIndex}
42+
</b>{" "}
43+
of <b>{itemCount ? itemCount : "many"}</b>
44+
</>
45+
)}
46+
perPageOptions={[
47+
{ title: "10", value: 10 },
48+
{ title: "20", value: 20 },
49+
{ title: "50", value: 50 },
50+
]}
51+
isSticky
52+
itemCount={itemCount}
53+
widgetId="indeterminate-loading"
54+
perPage={perPage}
55+
page={page}
56+
onSetPage={onSetPage}
57+
onPerPageSelect={onPerPageSelect}
58+
/>
59+
);
60+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Contributors to the Packit project.
2+
// SPDX-License-Identifier: MIT
3+
4+
import { createContext } from "react";
5+
6+
// set the defaults
7+
export const PackitPaginationContext = createContext({
8+
page: 1, // sane defaults
9+
perPage: 10, // sane defaults
10+
setPage: (_page: number) => {
11+
// Implement when using context
12+
},
13+
setPerPage: (_perPage: number) => {
14+
// Implement when using context
15+
},
16+
});

frontend/src/queries/project/projects.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Project } from "../../apiDefinitions";
55

66
interface fetchProjectsProps {
77
pageParam: number;
8+
perPage?: number;
89
signal?: AbortSignal;
910
forge?: string;
1011
namespace?: string;
@@ -13,14 +14,15 @@ interface fetchProjectsProps {
1314
// Fetch data from dashboard backend (or if we want, directly from the API)
1415
export const fetchProjects = async ({
1516
pageParam = 1,
17+
perPage = 10,
1618
signal,
1719
forge,
1820
namespace,
1921
}: fetchProjectsProps): Promise<Project[]> => {
2022
const projects = await fetch(
2123
`${import.meta.env.VITE_API_URL}/projects${forge ? "/" + forge : ""}${
2224
namespace ? "/" + namespace : ""
23-
}?page=${pageParam}`,
25+
}?page=${pageParam}&per_page=${perPage}`,
2426
{ signal },
2527
)
2628
.then((response) => response.json())
Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
// Copyright Contributors to the Packit project.
22
// SPDX-License-Identifier: MIT
33

4-
import { infiniteQueryOptions } from "@tanstack/react-query";
4+
import { queryOptions } from "@tanstack/react-query";
55
import { fetchProjects } from "./projects";
66

7-
export const projectsQueryOptions = (forge?: string, namespace?: string) =>
8-
infiniteQueryOptions({
9-
queryKey: ["projects", { forge, namespace }],
10-
queryFn: async ({ pageParam, signal }) =>
11-
await fetchProjects({ pageParam, signal, forge, namespace }),
12-
initialPageParam: 1,
13-
getNextPageParam: (lastPage, _allPages, lastPageParam) => {
14-
if (lastPage.length === 0) {
15-
return undefined;
16-
}
17-
return lastPageParam + 1;
18-
},
19-
getPreviousPageParam: (_firstPage, _allPages, firstPageParam) => {
20-
if (firstPageParam <= 1) {
21-
return undefined;
22-
}
23-
return firstPageParam - 1;
24-
},
7+
export const projectsQueryOptions = (
8+
pageParam: number,
9+
perPage: number = 20,
10+
forge?: string,
11+
namespace?: string,
12+
) =>
13+
queryOptions({
14+
queryKey: ["projects", { pageParam, perPage, forge, namespace }],
15+
queryFn: async ({ signal }) =>
16+
await fetchProjects({ pageParam, perPage, signal, forge, namespace }),
2517
});

0 commit comments

Comments
 (0)