All Articles

Creating a Reddit Clone Using React and GraphQL - 15

inner peace

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 gencommand. 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