Use dedicated pagination hook

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-19 13:43:18 +01:00
parent adb1dbbb39
commit 3cce6ea235
4 changed files with 278 additions and 296 deletions

View File

@@ -6,6 +6,7 @@ import {
useQueryLoader,
useMutation,
ConnectionHandler,
usePaginationFragment,
} from "react-relay";
import { useSearchParams } from "react-router";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -20,38 +21,46 @@ import { Helmet } from "react-helmet-async";
import { VendorListPageCreateVendorMutation } from "./__generated__/VendorListPageCreateVendorMutation.graphql";
import { VendorListPageDeleteVendorMutation } from "./__generated__/VendorListPageDeleteVendorMutation.graphql";
import { toast } from "@/hooks/use-toast";
import { VendorListPagePaginationQuery } from "./__generated__/VendorListPagePaginationQuery.graphql";
import { VendorListPage_vendors$key } from "./__generated__/VendorListPage_vendors.graphql";
const ITEMS_PER_PAGE = 25;
const vendorListPageQuery = graphql`
query VendorListPageQuery(
$first: Int
$after: CursorKey
$last: Int
$before: CursorKey
) {
query VendorListPageQuery($first: Int, $after: CursorKey, $last: Int, $before: CursorKey) {
currentOrganization: node(id: "AZSfP_xAcAC5IAAAAAAltA") {
id
... on Organization {
vendors(first: $first, after: $after, last: $last, before: $before)
@connection(key: "VendorListPageQuery_vendors") {
edges {
node {
id
name
createdAt
updatedAt
}
cursor
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
...VendorListPage_vendors
}
}
}
`;
const vendorListFragment = graphql`
fragment VendorListPage_vendors on Organization
@refetchable(queryName: "VendorListPagePaginationQuery") {
id
vendors(
first: $first
after: $after
last: $last
before: $before
) @connection(key: "VendorListPage_vendors") {
edges {
node {
id
name
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
`;
@@ -103,52 +112,46 @@ const vendorsList = [
];
function LoadAboveButton({
pageInfo,
isPending,
onPageChange,
isLoading,
hasMore,
onLoadMore,
}: {
pageInfo: {
hasNextPage: boolean;
hasPreviousPage: boolean;
} | null | undefined;
isPending: boolean;
onPageChange: (direction: "prev") => void;
isLoading: boolean;
hasMore: boolean;
onLoadMore: () => void;
}) {
return (
<div className="flex justify-center">
<Button
variant="outline"
onClick={() => onPageChange("prev")}
disabled={isPending || !pageInfo?.hasPreviousPage}
onClick={onLoadMore}
disabled={isLoading || !hasMore}
className="w-full"
>
{isPending ? "Loading..." : "Load above"}
{isLoading ? "Loading..." : "Load above"}
</Button>
</div>
);
}
function LoadBelowButton({
pageInfo,
isPending,
onPageChange,
isLoading,
hasMore,
onLoadMore,
}: {
pageInfo: {
hasNextPage: boolean;
hasPreviousPage: boolean;
} | null | undefined;
isPending: boolean;
onPageChange: (direction: "next") => void;
isLoading: boolean;
hasMore: boolean;
onLoadMore: () => void;
}) {
return (
<div className="flex justify-center">
<Button
variant="outline"
onClick={() => onPageChange("next")}
disabled={isPending || !pageInfo?.hasNextPage}
onClick={onLoadMore}
disabled={isLoading || !hasMore}
className="w-full"
>
{isPending ? "Loading..." : "Load below"}
{isLoading ? "Loading..." : "Load below"}
</Button>
</div>
);
@@ -156,17 +159,10 @@ function LoadBelowButton({
function VendorListContent({
queryRef,
onPageChange,
}: {
queryRef: PreloadedQuery<VendorListPageQueryType>;
onPageChange: (params: {
first?: number;
after?: string;
last?: number;
before?: string;
}) => void;
}) {
const data = usePreloadedQuery(vendorListPageQuery, queryRef);
const data = usePreloadedQuery<VendorListPageQueryType>(vendorListPageQuery, queryRef);
const [searchParams, setSearchParams] = useSearchParams();
const [isPending, startTransition] = useTransition();
const [searchTerm, setSearchTerm] = useState("");
@@ -174,41 +170,24 @@ function VendorListContent({
const [createVendor] = useMutation<VendorListPageCreateVendorMutation>(createVendorMutation);
const [deleteVendor] = useMutation<VendorListPageDeleteVendorMutation>(deleteVendorMutation);
const vendors = data.currentOrganization.vendors?.edges.map((edge) => edge.node) ?? [];
const pageInfo = data.currentOrganization.vendors?.pageInfo;
const {
data: vendorsConnection,
loadNext,
loadPrevious,
hasNext,
hasPrevious,
isLoadingNext,
isLoadingPrevious,
} = usePaginationFragment<VendorListPagePaginationQuery, VendorListPage_vendors$key>(vendorListFragment, data.currentOrganization);
const vendors = vendorsConnection.vendors.edges.map((edge) => edge.node) ?? [];
const pageInfo = vendorsConnection.vendors.pageInfo;
const fuse = new Fuse(vendorsList, {
keys: ["name"],
threshold: 0.3,
});
const handlePageChange = (direction: "next" | "prev") => {
if (!pageInfo) return;
if (direction === "next" && !pageInfo.hasNextPage) return;
if (direction === "prev" && !pageInfo.hasPreviousPage) return;
startTransition(() => {
const params =
direction === "next"
? { first: ITEMS_PER_PAGE, after: pageInfo.endCursor }
: { last: ITEMS_PER_PAGE, before: pageInfo.startCursor };
setSearchParams((prev) => {
if (direction === "next") {
prev.set("after", pageInfo.endCursor!);
prev.delete("before");
} else {
prev.set("before", pageInfo.startCursor!);
prev.delete("after");
}
return prev;
});
onPageChange(params);
});
};
return (
<div className="p-6 space-y-6">
<div className="space-y-1">
@@ -299,9 +278,18 @@ function VendorListContent({
</div>
<LoadAboveButton
pageInfo={pageInfo}
isPending={isPending}
onPageChange={() => handlePageChange("prev")}
isLoading={isLoadingPrevious}
hasMore={hasPrevious}
onLoadMore={() => {
startTransition(() => {
setSearchParams((prev) => {
prev.set("before", pageInfo?.startCursor || "");
prev.delete("after");
return prev;
});
loadPrevious(ITEMS_PER_PAGE);
});
}}
/>
<div className="space-y-2">
@@ -378,9 +366,18 @@ function VendorListContent({
</div>
<LoadBelowButton
pageInfo={pageInfo}
isPending={isPending}
onPageChange={() => handlePageChange("next")}
isLoading={isLoadingNext}
hasMore={hasNext}
onLoadMore={() => {
startTransition(() => {
setSearchParams((prev) => {
prev.set("after", pageInfo?.endCursor || "");
prev.delete("before");
return prev;
});
loadNext(ITEMS_PER_PAGE);
});
}}
/>
</div>
);
@@ -417,29 +414,7 @@ export default function VendorListPage() {
last: before ? ITEMS_PER_PAGE : undefined,
before: before || undefined,
});
}, [loadQuery, searchParams]);
const handlePageChange = ({
first,
after,
last,
before,
}: {
first?: number;
after?: string;
last?: number;
before?: string;
}) => {
loadQuery(
{
first,
after,
last,
before,
},
{ fetchPolicy: "network-only" },
);
};
}, [loadQuery]);
if (!queryRef) {
return <VendorListFallback />;
@@ -451,10 +426,7 @@ export default function VendorListPage() {
<title>Vendors - Probo Console</title>
</Helmet>
<Suspense fallback={<VendorListFallback />}>
<VendorListContent
queryRef={queryRef}
onPageChange={handlePageChange}
/>
<VendorListContent queryRef={queryRef} />
</Suspense>
</>
);

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<87314e850c396bef91d3fa31ec8a04b8>>
* @generated SignedSource<<ded948fa3baf7aa7846b5db641d8c49a>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -12,8 +12,10 @@ import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type VendorListPagePaginationQuery$variables = {
after?: any | null | undefined;
before?: any | null | undefined;
first?: number | null | undefined;
id: string;
last?: number | null | undefined;
};
export type VendorListPagePaginationQuery$data = {
readonly node: {
@@ -26,66 +28,90 @@ export type VendorListPagePaginationQuery = {
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
v2 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "first"
},
v3 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
},
v4 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
},
v5 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = {
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = [
v8 = [
{
"kind": "Variable",
"name": "after",
"variableName": "after"
},
{
"kind": "Variable",
"name": "before",
"variableName": "before"
},
{
"kind": "Variable",
"name": "first",
"variableName": "first"
},
{
"kind": "Variable",
"name": "last",
"variableName": "last"
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "VendorListPagePaginationQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"args": (v5/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
@@ -105,26 +131,32 @@ return {
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v4/*: any*/),
(v3/*: any*/)
],
"kind": "Operation",
"name": "VendorListPagePaginationQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"args": (v5/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v4/*: any*/),
"args": (v8/*: any*/),
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
@@ -146,7 +178,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
@@ -168,7 +200,7 @@ return {
"name": "updatedAt",
"storageKey": null
},
(v2/*: any*/)
(v6/*: any*/)
],
"storageKey": null
},
@@ -226,7 +258,7 @@ return {
},
{
"alias": null,
"args": (v4/*: any*/),
"args": (v8/*: any*/),
"filters": null,
"handle": "connection",
"key": "VendorListPage_vendors",
@@ -243,16 +275,16 @@ return {
]
},
"params": {
"cacheID": "73f269ef13a930c0629d857caae56a34",
"cacheID": "3ce437d0d8e4415db28f78cf8c9aac7c",
"id": null,
"metadata": {},
"name": "VendorListPagePaginationQuery",
"operationKind": "query",
"text": "query VendorListPagePaginationQuery(\n $after: CursorKey\n $first: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorListPage_vendors\n id\n }\n}\n\nfragment VendorListPage_vendors on Organization {\n vendors(first: $first, after: $after) {\n edges {\n node {\n id\n name\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n"
"text": "query VendorListPagePaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorListPage_vendors\n id\n }\n}\n\nfragment VendorListPage_vendors on Organization {\n id\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "753e6b32cc21645852de27cf6f23c769";
(node as any).hash = "7f084d24023bc4dd68d22cdd6e75f174";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<874e734a238db8fc4ab486da7d49ceee>>
* @generated SignedSource<<23fdb7f13c0e5f5c25485a6c4800dcb5>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,6 +9,7 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type VendorListPageQuery$variables = {
after?: any | null | undefined;
before?: any | null | undefined;
@@ -18,23 +19,7 @@ export type VendorListPageQuery$variables = {
export type VendorListPageQuery$data = {
readonly currentOrganization: {
readonly id: string;
readonly vendors?: {
readonly edges: ReadonlyArray<{
readonly cursor: any;
readonly node: {
readonly createdAt: any;
readonly id: string;
readonly name: string;
readonly updatedAt: any;
};
}>;
readonly pageInfo: {
readonly endCursor: any | null | undefined;
readonly hasNextPage: boolean;
readonly hasPreviousPage: boolean;
readonly startCursor: any | null | undefined;
};
};
readonly " $fragmentSpreads": FragmentRefs<"VendorListPage_vendors">;
};
};
export type VendorListPageQuery = {
@@ -85,99 +70,6 @@ v6 = {
"storageKey": null
},
v7 = [
{
"alias": null,
"args": null,
"concreteType": "VendorEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Vendor",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
(v6/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
}
],
v8 = [
{
"kind": "Variable",
"name": "after",
@@ -224,14 +116,9 @@ return {
"kind": "InlineFragment",
"selections": [
{
"alias": "vendors",
"args": null,
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "__VendorListPageQuery_vendors_connection",
"plural": false,
"selections": (v7/*: any*/),
"storageKey": null
"kind": "FragmentSpread",
"name": "VendorListPage_vendors"
}
],
"type": "Organization",
@@ -270,20 +157,112 @@ return {
"selections": [
{
"alias": null,
"args": (v8/*: any*/),
"args": (v7/*: any*/),
"concreteType": "VendorConnection",
"kind": "LinkedField",
"name": "vendors",
"plural": false,
"selections": (v7/*: any*/),
"selections": [
{
"alias": null,
"args": null,
"concreteType": "VendorEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Vendor",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
(v6/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": (v8/*: any*/),
"args": (v7/*: any*/),
"filters": null,
"handle": "connection",
"key": "VendorListPageQuery_vendors",
"key": "VendorListPage_vendors",
"kind": "LinkedHandle",
"name": "vendors"
}
@@ -297,28 +276,16 @@ return {
]
},
"params": {
"cacheID": "079490495e96d3609a263570f185673b",
"cacheID": "b910bb15ebb2e9b5aae2f20e0f5266db",
"id": null,
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "bidirectional",
"path": [
"currentOrganization",
"vendors"
]
}
]
},
"metadata": {},
"name": "VendorListPageQuery",
"operationKind": "query",
"text": "query VendorListPageQuery(\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n currentOrganization: node(id: \"AZSfP_xAcAC5IAAAAAAltA\") {\n __typename\n id\n ... on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n}\n"
"text": "query VendorListPageQuery(\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n currentOrganization: node(id: \"AZSfP_xAcAC5IAAAAAAltA\") {\n __typename\n id\n ... on Organization {\n ...VendorListPage_vendors\n }\n }\n}\n\nfragment VendorListPage_vendors on Organization {\n id\n vendors(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n name\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n}\n"
}
};
})();
(node as any).hash = "9613eb9b1e34b2ebffda69c5909f5f5b";
(node as any).hash = "a1f88bf5287abaffb051993051d0e2a7";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<98591652ea931191ef7210e363e0adad>>
* @generated SignedSource<<36562961e8a3be3e68279a6d56f3ef1a>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -52,18 +52,26 @@ return {
"kind": "RootArgument",
"name": "after"
},
{
"kind": "RootArgument",
"name": "before"
},
{
"kind": "RootArgument",
"name": "first"
},
{
"kind": "RootArgument",
"name": "last"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "first",
"cursor": "after",
"direction": "forward",
"count": null,
"cursor": null,
"direction": "bidirectional",
"path": (v0/*: any*/)
}
],
@@ -73,7 +81,10 @@ return {
"count": "first",
"cursor": "after"
},
"backward": null,
"backward": {
"count": "last",
"cursor": "before"
},
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
@@ -203,6 +214,6 @@ return {
};
})();
(node as any).hash = "f61c6a2996a9fe95b71d9c0c2fe0530c";
(node as any).hash = "7f084d24023bc4dd68d22cdd6e75f174";
export default node;