From this blog post, we are going to set up the pagination. So we need to seed a lot of data into the database. We can create mock data using mockaroo site and export those data as the SQL query.
Then run the create migration command to generate the migration file in the server application.
npx typeorm migration:create -n FakePost
Inside that migration file, you will see 2 methods called up
and down
. Add this code line to up
method.
await queryRunner.query(`
... mockaroo queries goes here `);
Then change the server index file to run this migration file. You can move that file into migration
folder. Add the migration property to createConnection
method.
migrations: [path.join(__dirname, "./migrations/*")],
Then below that method add this line of code to run the migration.
await conn.runMigrations();
At this point, we are not returning the text of the post. But we don’t want to show the full text on the home page, on the home page we can only show a limited number of characters.
We can Resolver annotation to @Resolver(Post)
and we are adding FiledResolver.
@FieldResolver(() => String)
textSnippet(@Root() root: Post) {
return root.text.slice(0, 50);
}
Also, we are using cursorPagination
method that taking last post timestamp and return the older posts. We can change the posts
method to user query builder and return result as the number of limit. Here we are taking 2 parameters as limit
and cursor
.
async posts(
@Arg("limit", () => Int) limit: number,
@Arg("cursor", () => String, { nullable: true }) cursor: string | null
): Promise<Post[]> {
// previously we took all the posts
// return await Post.find();
// using query builder
const realLimit = Math.min(50, limit);
const qb = getConnection()
.getRepository(Post)
.createQueryBuilder("p")
.orderBy('"createdAt"', "DESC")
.take(realLimit);
if (cursor) {
// take the old post using cursor
qb.where('"createdAt" < :cursor', {
cursor: new Date(parseInt(cursor)),
});
}
return qb.getMany();
}
Now our back-end code is completed. Now we need to change front-end graphql
query to match with these 2 parameters.
query Posts($limit: Int!, $cursor: String) {
posts(cursor: $cursor, limit: $limit) {
id
createdAt
updatedAt
title
textSnippet
}
}
After changing this graphql
query, execute the yarn gen
command. It will update the generated methods to retrieve the posts.
Now we can change the outlook of the post by adding some chakra-ui
components.
We can add a header and re-arrange the Create Post link.
<Flex align="center">
<Heading>Reddit Clone</Heading>
<NextLink href="/create-post">
<Link ml="auto">Create Post</Link>
</NextLink>
</Flex>
There are a few scenarios that we need to handle. One is if there is no data and still fetching, we need to show the below message.
if (!fetching && !data) {
return <div>there is some error in graphql query</div>;
}
To use fetching
we need to assign it from graphql
query.
const [{ data, fetching }] = usePostsQuery({
// ... rest of the code
We can update the post
UI as below to show post with its post snippet.
<Stack spacing={8}>
{data!.posts.map((p) => {
return (
<Box key={p.id} p={5} shadow="md" borderWidth="1px">
<Heading fontSize="xl">{p.title}</Heading>
<Text mt={4}>{p.textSnippet} </Text>
</Box>
);
})}
</Stack>
Then if there is data we can show a button to load more post. Above ending </Layout>
tag add below code.
{data ? (
<Flex>
<Button onClick={() => { }); } m="auto" my={8} isLoading={fetching} >
load more
</Button>
</Flex>
) : null}
Now we are adding the pagination resolver from cache to createUrqlClient
. This is the function that append the post in the cache to the new posts.
const cursorPagination = (): Resolver => {
return (_parent, fieldArgs, cache, info) => {
const { parentKey: entityKey, fieldName } = info;
const allFields = cache.inspectFields(entityKey);
console.log("allFields: ", allFields);
const fieldInfos = allFields.filter((info) => info.fieldName === fieldName);
const size = fieldInfos.length;
if (size === 0) {
return undefined;
}
const fieldKey = `${fieldName}(${stringifyVariables(fieldArgs)})`;
const isItInTheCache = cache.resolve(entityKey, fieldKey);
info.partial = !isItInTheCache;
const results: string[] = [];
fieldInfos.forEach((fi) => {
const data = cache.resolve(entityKey, fi.fieldKey) as string[];
console.log(data)
results.push(...data);
});
return results;
};
};
We need to set this in to exchange as cacheExchange
.
resolvers: {
Query: {
posts: cursorPagination(),
},
},
Now we can add the onClick
functionality. We can use the state to set the cursor. First, create the initial statue for the cursor
.
const [variables, setVariables] = useState({
limit: 10,
cursor: null as null | string,
});
Then once we click the button change the cursor to last post’s timestamp. This way we can load the new posts.
<Button
onClick={() => {
setVariables({
limit: variables.limit,
cursor: data.posts[data.posts.length - 1].createdAt,
});
}}
m="auto"
my={8}
isLoading={fetching}
>
// ...rest of the code
Now we can see it will update the new posts in the view.
Thanks for reading this. If you have anything to ask regarding this please leave a comment here. Also, I wrote this according to my understanding. So if any point is wrong, don’t hesitate to correct me. I really appreciate you. That’s for today friends. See you soon. Thank you.
References:
This article series based on the Ben Award - Fullstack React GraphQL TypeScript Tutorial. This is amazing tutorial and I highly recommend you to check that out.
Main image credit