Skip to content

Commit 98b86ff

Browse files
committed
Refactor PaginatedDependencies to use loader-resolved data
- Remove Promise.all, <Await>, Suspense, and useMemo from the component - Simplify Props to accept resolved objects instead of promises - Use URL query param as single source of truth for page - Remove local/debounced state, refs, and navigation effects - Ensure stable keys for list items - Component now renders cleanly with correct pagination Previously, mixing unresolved promises, local state, and debouncing caused stale/mis-synced data and infinite re-render loops. Now the component is simpler, predictable, and fully driven by loader data and URL. Refs. TS-2750
1 parent 41d8862 commit 98b86ff

File tree

2 files changed

+43
-159
lines changed

2 files changed

+43
-159
lines changed
Lines changed: 41 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,59 @@
1-
import { memo, Suspense, useEffect, useMemo, useRef, useState } from "react";
21
import "./PaginatedDependencies.css";
32
import { Heading, NewPagination, SkeletonBox } from "@thunderstore/cyberstorm";
43
import { ListingDependency } from "../ListingDependency/ListingDependency";
5-
import { Await, useNavigationType, useSearchParams } from "react-router";
6-
import { useDebounce } from "use-debounce";
7-
import type { getPackageVersionDetails } from "@thunderstore/dapper-ts/src/methods/packageVersion";
8-
import type { getPackageVersionDependencies } from "@thunderstore/dapper-ts/src/methods/package";
9-
import { setParamsBlobValue } from "cyberstorm/utils/searchParamsUtils";
4+
import { useSearchParams } from "react-router";
105

116
interface Props {
12-
version:
13-
| Awaited<ReturnType<typeof getPackageVersionDetails>>
14-
| ReturnType<typeof getPackageVersionDetails>;
15-
dependencies:
16-
| Awaited<ReturnType<typeof getPackageVersionDependencies>>
17-
| ReturnType<typeof getPackageVersionDependencies>;
7+
version: any; // Replace 'any' with the appropriate type for version
8+
dependencies: any; // Replace 'any' with the appropriate type for dependencies
189
pageSize?: number;
1910
siblingCount?: number;
2011
}
2112

22-
export const PaginatedDependencies = memo(function PaginatedDependencies(
23-
props: Props
24-
) {
25-
const navigationType = useNavigationType();
26-
13+
export function PaginatedDependencies({
14+
version,
15+
dependencies,
16+
pageSize = 20, // Default page size from backend
17+
siblingCount = 4,
18+
}: Props) {
2719
const [searchParams, setSearchParams] = useSearchParams();
20+
const page = Number(searchParams.get("page") ?? 1);
2821

29-
const initialParams = searchParamsToBlob(searchParams);
30-
31-
const [searchParamsBlob, setSearchParamsBlob] =
32-
useState<SearchParamsType>(initialParams);
33-
34-
const [currentPage, setCurrentPage] = useState(
35-
searchParams.get("page") ? Number(searchParams.get("page")) : 1
36-
);
37-
38-
const [debouncedSearchParamsBlob] = useDebounce(searchParamsBlob, 300, {
39-
maxWait: 300,
40-
});
41-
42-
const searchParamsBlobRef = useRef(debouncedSearchParamsBlob);
43-
44-
const searchParamsRef = useRef(searchParams);
45-
useEffect(() => {
46-
if (navigationType === "POP") {
47-
if (searchParamsRef.current !== searchParams) {
48-
const spb = searchParamsToBlob(searchParams);
49-
setSearchParamsBlob(spb);
50-
setCurrentPage(spb.page);
51-
searchParamsRef.current = searchParams;
52-
}
53-
searchParamsBlobRef.current = searchParamsToBlob(searchParams);
54-
}
55-
}, [searchParams]);
56-
57-
useEffect(() => {
58-
if (
59-
navigationType !== "POP" ||
60-
(navigationType === "POP" &&
61-
searchParamsBlobRef.current !== debouncedSearchParamsBlob)
62-
) {
63-
if (searchParamsBlobRef.current !== debouncedSearchParamsBlob) {
64-
const oldPage = searchParams.get("page")
65-
? Number(searchParams.get("page"))
66-
: 1;
67-
// Page number
68-
if (oldPage !== debouncedSearchParamsBlob.page) {
69-
if (debouncedSearchParamsBlob.page === 1) {
70-
searchParams.delete("page");
71-
setCurrentPage(1);
72-
} else {
73-
searchParams.set("page", String(debouncedSearchParamsBlob.page));
74-
setCurrentPage(debouncedSearchParamsBlob.page);
75-
}
76-
}
77-
const uncommittedSearchParams = searchParamsToBlob(searchParams);
78-
79-
if (
80-
navigationType !== "POP" ||
81-
(navigationType === "POP" &&
82-
!compareSearchParamBlobs(
83-
uncommittedSearchParams,
84-
searchParamsBlobRef.current
85-
) &&
86-
compareSearchParamBlobs(
87-
uncommittedSearchParams,
88-
debouncedSearchParamsBlob
89-
))
90-
) {
91-
setSearchParams(searchParams, { preventScrollReset: true });
92-
}
93-
searchParamsBlobRef.current = debouncedSearchParamsBlob;
94-
}
95-
}
96-
}, [debouncedSearchParamsBlob]);
97-
98-
const versionAndDependencies = useMemo(
99-
() => Promise.all([props.version, props.dependencies]),
100-
[currentPage]
101-
);
22+
const handlePageChange = (nextPage: number) => {
23+
const next = new URLSearchParams(searchParams);
24+
nextPage === 1 ? next.delete("page") : next.set("page", String(nextPage));
25+
setSearchParams(next, { preventScrollReset: true });
26+
};
10227

10328
return (
10429
<div className="paginated-dependencies">
105-
<Suspense
106-
fallback={<SkeletonBox className="paginated-dependencies__skeleton" />}
107-
>
108-
<Await
109-
resolve={versionAndDependencies}
110-
errorElement={
111-
<div>Error occurred while loading required dependencies</div>
112-
}
113-
>
114-
{(resolvedValue) => {
115-
return (
116-
<>
117-
<div className="paginated-dependencies__title">
118-
<Heading csLevel="3" csSize="3">
119-
Required mods ({resolvedValue[0].dependency_count})
120-
</Heading>
121-
<span className="paginated-dependencies__description">
122-
This package requires the following packages to work.
123-
</span>
124-
</div>
125-
<div className="paginated-dependencies__body">
126-
{resolvedValue[1].results.map((dep, key) => {
127-
return <ListingDependency key={key} dependency={dep} />;
128-
})}
129-
</div>
130-
<NewPagination
131-
currentPage={currentPage}
132-
pageSize={props.pageSize ?? 20}
133-
totalCount={resolvedValue[0].dependency_count}
134-
onPageChange={setParamsBlobValue(
135-
setSearchParamsBlob,
136-
searchParamsBlob,
137-
"page"
138-
)}
139-
siblingCount={props.siblingCount ?? 4}
140-
/>
141-
</>
142-
);
143-
}}
144-
</Await>
145-
</Suspense>
30+
<div className="paginated-dependencies__title">
31+
<Heading csLevel="3" csSize="3">
32+
Required mods ({version.dependency_count})
33+
</Heading>
34+
<span className="paginated-dependencies__description">
35+
This package requires the following packages to work.
36+
</span>
37+
</div>
38+
39+
<div className="paginated-dependencies__body">
40+
{dependencies.results.map((dep, idx) => (
41+
<ListingDependency
42+
key={`${dep.name}-${dep.version_number}-${idx}`}
43+
dependency={dep}
44+
/>
45+
))}
46+
</div>
47+
48+
<NewPagination
49+
currentPage={page}
50+
pageSize={pageSize}
51+
totalCount={version.dependency_count}
52+
onPageChange={handlePageChange}
53+
siblingCount={siblingCount}
54+
/>
14655
</div>
14756
);
148-
});
57+
}
14958

15059
PaginatedDependencies.displayName = "PaginatedDependencies";
151-
152-
export type SearchParamsType = {
153-
page: number;
154-
};
155-
156-
export const compareSearchParamBlobs = (
157-
b1: SearchParamsType,
158-
b2: SearchParamsType
159-
) => {
160-
if (b1.page !== b2.page) return false;
161-
return true;
162-
};
163-
164-
export const searchParamsToBlob = (searchParams: URLSearchParams) => {
165-
const initialPage = searchParams.get("page");
166-
167-
return {
168-
page:
169-
initialPage &&
170-
!Number.isNaN(Number.parseInt(initialPage)) &&
171-
Number.isSafeInteger(Number.parseInt(initialPage))
172-
? Number.parseInt(initialPage)
173-
: 1,
174-
};
175-
};

apps/cyberstorm-remix/app/p/tabs/Required/Required.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ export async function clientLoader({ params, request }: LoaderFunctionArgs) {
5959
);
6060

6161
return {
62-
version: dapper.getPackageVersionDetails(
62+
version: await dapper.getPackageVersionDetails(
6363
params.namespaceId,
6464
params.packageId,
6565
listing.latest_version_number
6666
),
67-
dependencies: dapper.getPackageVersionDependencies(
67+
dependencies: await dapper.getPackageVersionDependencies(
6868
params.namespaceId,
6969
params.packageId,
7070
listing.latest_version_number,

0 commit comments

Comments
 (0)