Skip to content

Commit 9725318

Browse files
authored
Merge pull request #6 from supabase-community/feat-pagination
2 parents 9a604bd + 9220107 commit 9725318

8 files changed

Lines changed: 154 additions & 26 deletions

File tree

app/lib/comment-item.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function CommentItem(props: {
5656
<div className="flex space-x-3 py-4">
5757
<img
5858
className="h-6 w-6 rounded-full"
59-
src={props.comment.profile?.avatarUrl}
59+
src={props.comment.profile?.avatarUrl ?? undefined}
6060
alt=""
6161
/>
6262
<div className="flex-1 space-y-1">

app/lib/use-paginated-query.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from "react";
2+
import { useQuery, UseQueryArgs, UseQueryResponse } from "urql";
3+
4+
/**
5+
* Urql only supports "merge/infinite" pagination by adoptinh the GraphCache (a global normalized cache),
6+
* which certainly is an overkill for this demo.
7+
*
8+
* This hook wraps `useQuery` from urql and adds a light-weight merge previous and current result API.
9+
*/
10+
export function usePaginatedQuery<Data = any, Variables = object>(
11+
args: UseQueryArgs<Variables, Data> & {
12+
/**
13+
* Merge the old result with the new result.
14+
*/
15+
mergeResult: (oldData: Data, newData: Data) => Data;
16+
}
17+
): UseQueryResponse<Data, Variables> {
18+
const [query, queryFn] = useQuery(args);
19+
20+
const { data, ...rest } = query;
21+
22+
const mergeRef = React.useRef({ current: data, last: data });
23+
24+
if (
25+
data &&
26+
mergeRef.current.current &&
27+
query.data !== mergeRef.current.last
28+
) {
29+
mergeRef.current.current = args.mergeResult(mergeRef.current.current, data);
30+
}
31+
32+
if (data != null && mergeRef.current.current == null) {
33+
mergeRef.current.current = data;
34+
}
35+
36+
mergeRef.current.last = query.data;
37+
38+
return [
39+
{
40+
...rest,
41+
data: mergeRef.current.current,
42+
},
43+
queryFn,
44+
];
45+
}

app/pages/account.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,12 @@ const Account: NextPage = () => {
4040

4141
if (profile) {
4242
return <AccountForm profile={profile} />;
43-
} else {
44-
{
45-
return (
46-
<div className="w-full">
47-
profileQuery.fetching && <Loading />
48-
</div>
49-
);
50-
}
5143
}
5244

45+
return (
46+
<div className="w-full">{profileQuery.fetching ? <Loading /> : null}</div>
47+
);
48+
5349
return null;
5450
};
5551

app/pages/comments.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@ import { Button } from "@supabase/ui";
22
import type { NextPage } from "next";
33
import Head from "next/head";
44
import Image from "next/image";
5+
import React from "react";
56
import { useQuery } from "urql";
67
import { gql } from "../gql";
78
import { CommentItem } from "../lib/comment-item";
89
import { Container } from "../lib/container";
910
import { Loading } from "../lib/loading";
1011
import { MainSection } from "../lib/main-section";
12+
import { usePaginatedQuery } from "../lib/use-paginated-query";
1113

1214
const CommentsRouteQuery = gql(/* GraphQL */ `
13-
query CommentsRouteQuery {
15+
query CommentsRouteQuery($after: Cursor) {
1416
comments: commentCollection(
1517
orderBy: [{ createdAt: DescNullsFirst }]
1618
first: 15
19+
after: $after
1720
) {
1821
pageInfo {
1922
hasNextPage
23+
endCursor
2024
}
2125
edges {
2226
cursor
@@ -30,7 +34,26 @@ const CommentsRouteQuery = gql(/* GraphQL */ `
3034
`);
3135

3236
const Comments: NextPage = () => {
33-
const [commentsQuery] = useQuery({ query: CommentsRouteQuery });
37+
const [lastCursor, setLastCursor] = React.useState<string | undefined>(
38+
undefined
39+
);
40+
const [commentsQuery] = usePaginatedQuery({
41+
query: CommentsRouteQuery,
42+
variables: {
43+
after: lastCursor,
44+
},
45+
mergeResult(oldData, newData) {
46+
return {
47+
...oldData,
48+
...newData,
49+
comments: {
50+
...oldData.comments!,
51+
...newData.comments!,
52+
edges: [...oldData.comments!.edges, ...newData.comments!.edges],
53+
},
54+
};
55+
},
56+
});
3457

3558
return (
3659
<Container>
@@ -43,16 +66,25 @@ const Comments: NextPage = () => {
4366

4467
<section className="text-gray-600 body-font overflow-hidden w-full">
4568
<div className="container px-3 py-24 mx-auto">
46-
{commentsQuery.fetching && <Loading />}
4769
<div className="-my-8 divide-y-2 divide-gray-100">
4870
{commentsQuery?.data?.comments?.edges.map((edge) => (
4971
<CommentItem comment={edge.node!} key={edge.cursor} />
5072
))}
5173
</div>
74+
{commentsQuery.fetching ? <Loading /> : null}
5275
</div>
5376
{commentsQuery.data?.comments?.pageInfo.hasNextPage ? (
5477
<div className="flex justify-center content-center">
55-
<Button>Load more.</Button>
78+
<Button
79+
onClick={() =>
80+
setLastCursor(
81+
commentsQuery.data?.comments?.pageInfo.endCursor ??
82+
undefined
83+
)
84+
}
85+
>
86+
Load more.
87+
</Button>
5688
</div>
5789
) : null}
5890
</section>

app/pages/index.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
11
import { Auth, Button } from "@supabase/ui";
22
import type { NextPage } from "next";
33
import Head from "next/head";
4-
import { useQuery } from "urql";
4+
import React from "react";
55
import { gql } from "../gql";
66
import { Container } from "../lib/container";
77
import { FeedItem } from "../lib/feed-item";
88
import { Loading } from "../lib/loading";
99
import { MainSection } from "../lib/main-section";
1010
import { noopUUID } from "../lib/noop-uuid";
11+
import { usePaginatedQuery } from "../lib/use-paginated-query";
1112

1213
const IndexRouteQuery = gql(/* GraphQL */ `
13-
query IndexRouteQuery($profileId: UUID!) {
14+
query IndexRouteQuery($profileId: UUID!, $after: Cursor) {
1415
feed: postCollection(
1516
orderBy: [
1617
{ voteRank: AscNullsFirst }
1718
{ score: DescNullsFirst }
1819
{ createdAt: DescNullsFirst }
1920
]
2021
first: 15
22+
after: $after
2123
) {
2224
pageInfo {
2325
hasNextPage
26+
endCursor
2427
}
2528
edges {
2629
cursor
@@ -35,10 +38,25 @@ const IndexRouteQuery = gql(/* GraphQL */ `
3538

3639
const Home: NextPage = () => {
3740
const { user } = Auth.useUser();
38-
const [indexQuery] = useQuery({
41+
const [lastCursor, setLastCursor] = React.useState<string | undefined>(
42+
undefined
43+
);
44+
const [indexQuery] = usePaginatedQuery({
3945
query: IndexRouteQuery,
4046
variables: {
4147
profileId: user?.id ?? noopUUID,
48+
after: lastCursor,
49+
},
50+
mergeResult(oldData, newData) {
51+
return {
52+
...oldData,
53+
...newData,
54+
feed: {
55+
...oldData.feed!,
56+
...newData.feed!,
57+
edges: [...oldData.feed!.edges, ...newData.feed!.edges],
58+
},
59+
};
4260
},
4361
});
4462

@@ -54,15 +72,23 @@ const Home: NextPage = () => {
5472
<section className="text-gray-600 body-font overflow-hidden w-full">
5573
<div className="container px-3 py-24 mx-auto">
5674
<div className="-my-8">
57-
{indexQuery.fetching && <Loading />}
5875
{indexQuery?.data?.feed?.edges.map((edge) => (
5976
<FeedItem post={edge.node!} key={edge.cursor} />
6077
))}
78+
{indexQuery.fetching ? <Loading /> : null}
6179
</div>
6280
</div>
6381
{indexQuery.data?.feed?.pageInfo.hasNextPage ? (
6482
<div className="flex justify-center content-center">
65-
<Button>Load more.</Button>
83+
<Button
84+
onClick={() => {
85+
setLastCursor(
86+
indexQuery.data?.feed?.pageInfo.endCursor ?? undefined
87+
);
88+
}}
89+
>
90+
Load more.
91+
</Button>
6692
</div>
6793
) : null}
6894
</section>

app/pages/item/[postId].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const Item: NextPage = () => {
104104
<Container>
105105
<MainSection>
106106
<div className="h-screen w-full">
107-
{itemRouteQuery.fetching && <Loading />}
107+
{itemRouteQuery.fetching ? <Loading /> : null}
108108

109109
{post?.node == null ? null : (
110110
<>

app/pages/newest.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
import { Auth, Button } from "@supabase/ui";
22
import type { NextPage } from "next";
33
import Head from "next/head";
4-
import { useQuery } from "urql";
4+
import React from "react";
55
import { gql } from "../gql";
66
import { Container } from "../lib/container";
77
import { FeedItem } from "../lib/feed-item";
88
import { Loading } from "../lib/loading";
99
import { MainSection } from "../lib/main-section";
1010
import { noopUUID } from "../lib/noop-uuid";
11+
import { usePaginatedQuery } from "../lib/use-paginated-query";
1112

1213
const NewestRouteQuery = gql(/* GraphQL */ `
13-
query NewestRouteQuery($profileId: UUID!) {
14-
feed: postCollection(orderBy: [{ createdAt: DescNullsFirst }], first: 15) {
14+
query NewestRouteQuery($profileId: UUID!, $after: Cursor) {
15+
feed: postCollection(
16+
orderBy: [{ createdAt: DescNullsFirst }]
17+
first: 15
18+
after: $after
19+
) {
1520
pageInfo {
1621
hasNextPage
22+
endCursor
1723
}
1824
edges {
1925
cursor
@@ -28,10 +34,25 @@ const NewestRouteQuery = gql(/* GraphQL */ `
2834

2935
const Newest: NextPage = () => {
3036
const { user } = Auth.useUser();
31-
const [newestQuery] = useQuery({
37+
const [lastCursor, setLastCursor] = React.useState<string | undefined>(
38+
undefined
39+
);
40+
const [newestQuery] = usePaginatedQuery({
3241
query: NewestRouteQuery,
3342
variables: {
3443
profileId: user?.id ?? noopUUID,
44+
after: lastCursor,
45+
},
46+
mergeResult(oldData, newData) {
47+
return {
48+
...oldData,
49+
...newData,
50+
feed: {
51+
...oldData.feed!,
52+
...newData.feed!,
53+
edges: [...oldData.feed!.edges, ...newData.feed!.edges],
54+
},
55+
};
3556
},
3657
});
3758

@@ -47,15 +68,23 @@ const Newest: NextPage = () => {
4768
<section className="text-gray-600 body-font overflow-hidden w-full">
4869
<div className="container px-5 py-24 mx-auto">
4970
<div className="-my-8">
50-
{newestQuery.fetching && <Loading />}
5171
{newestQuery?.data?.feed?.edges.map((edge) => (
5272
<FeedItem post={edge.node!} key={edge.cursor} />
5373
))}
5474
</div>
75+
{newestQuery.fetching ? <Loading /> : null}
5576
</div>
5677
{newestQuery.data?.feed?.pageInfo.hasNextPage ? (
5778
<div className="flex justify-center content-center">
58-
<Button>Load more.</Button>
79+
<Button
80+
onClick={() => {
81+
setLastCursor(
82+
newestQuery.data?.feed?.pageInfo.endCursor ?? undefined
83+
);
84+
}}
85+
>
86+
Load more.
87+
</Button>
5988
</div>
6089
) : null}
6190
</section>

app/pages/profile/[profileId].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const Profile: NextPage = () => {
7777

7878
<MainSection>
7979
<div className="w-full">
80-
{profileQuery.fetching && <Loading />}
80+
{profileQuery.fetching ? <Loading /> : null}
8181
{profile == null ? null : (
8282
<section className="text-gray-600 body-font overflow-hidden">
8383
<div className="container px-5 py-24 pt-10 mx-auto">

0 commit comments

Comments
 (0)