Implementing Infinite scroll using NextJS, Prima, and React-Query
Tags: react, nextjs, javascript, webdev
Published: 2022-01-04 12:54:22

## Intro
Hello everyone, in this article lets see how can we build an infinite scroll UI pattern using NextJs, Prisma, and React-Query

## Final result

![My Video.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1632231679283/1TzlElNZhA.gif)

TLDR: Link to [code](https://github.com/sairaj2119/infinite-scroll-ui)

TTLDR: Link to [video](https://youtu.be/wkClKGnvsDE)

## Project setup
Open an empty folder in your preferred editor and create a NextJS project by typing
`npx create-next-app . --ts` in the command line of that project. This will create a NextJS project with typescript in the current folder, now let's install some dependencies 

`npm install @prisma/client axios react-intersection-observer react-query`

`npm install -D prisma faker @types/faker`

## Initializing Prisma
Open a terminal in the root directory and type`npx prisma init` this will initialize a Prisma project by creating a folder named  `prisma` having `schema.prisma` file in it and in the root directory we can see a `.env` file with `DATABASE_URL` environment variable which is a connection string to the database, in this article we will use postgres, so database URL should look something this.

`"postgresql://<USER>:<PASSWORD>@localhost:5432/<DATABASE>?schema=public"
`
Change the connection URL according to your configuration(make sure you do this part without any typos if not Prisma will not be able to connect to the database)

open `schema.prisma` file and paste the below code which is a basic model for a `Post `

```
model Post {
  id         Int        @id @default(autoincrement())
  title      String
  createdAt  DateTime   @default(now())
}

```
This in itself will not create `Post` table in out database we have to migrate the changes by using the following command

`npx prisma migrate dev --name=init`

This will create `Post` table in the database specified (if there is an error in connection URL this step will fail, make sure you have no typos in `DATABASE_URL`) and generates types for us to work with.

## Seeding database
Create a file `seed.js` in `prisma` directory and lets write a seed script to fill out database with some fake data
```
const { PrismaClient } = require('@prisma/client')
const { lorem } = require('faker')

const prisma = new PrismaClient()

const seed = async () => {
  const postPromises = []

  new Array(50).fill(0).forEach((_) => {
    postPromises.push(
      prisma.post.create({
        data: {
          title: lorem.sentence(),
        },
      })
    )
  })
  const posts = await Promise.all(postPromises)
  console.log(posts)
}

seed()
  .catch((err) => {
    console.error(err)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

```

Add the below key-value pair to `package.json`

```
 "prisma": {
    "seed": "node ./prisma/seed.js"
  }
```
Then run `npx prisma db seed` this will run `seed.js` file we will have `50` posts in ourdatabase which is quite enough to implement infinite scroll

## Creating API route
Lets now write an API route so that we can get our posts, create a file `post.ts` inside `/pages/api`

```
import type { NextApiRequest, NextApiResponse } from 'next'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

type Post = {
  id: number
  title: string
  createdAt: Date
}

interface Data {
  posts: Post[]
  nextId: number | undefined
}

export default async (req: NextApiRequest, res: NextApiResponse<Data>) => {
  if (req.method === 'GET') {
    const limit = 5
    const cursor = req.query.cursor ?? ''
    const cursorObj = cursor === '' ? undefined : { id: parseInt(cursor as string, 10) }

    const posts = await prisma.post.findMany({
      skip: cursor !== '' ? 1 : 0,
      cursor: cursorObj,
      take: limit,
    })
    return res.json({ posts, nextId: posts.length === limit ? posts[limit - 1].id : undefined })
  }
}

```
The above API route on a `GET` request checks for a query parameter `cursor` if `cursor` is empty we just return `limit` number of posts, but if the cursor is not empty we `skip` one post and send `limit` posts, along with posts we also send `nextId` which will be used by React-Query to send further requests 

## Using useInfiniteQuery
In `index.tsx` of `pages` directory use the code below
```
import React, { useEffect } from 'react'
import { useInfiniteQuery } from 'react-query'
import axios from 'axios'
import { useInView } from 'react-intersection-observer'

export default function Home() {
  const { ref, inView } = useInView()

  const { isLoading, isError, data, error, isFetchingNextPage, fetchNextPage, hasNextPage } =
    useInfiniteQuery(
      'posts',
      async ({ pageParam = '' }) => {
        await new Promise((res) => setTimeout(res, 1000))
        const res = await axios.get('/api/post?cursor=' + pageParam)
        return res.data
      },
      {
        getNextPageParam: (lastPage) => lastPage.nextId ?? false,
      }
    )

  useEffect(() => {
    if (inView && hasNextPage) {
      fetchNextPage()
    }
  }, [inView])

  if (isLoading) return <div className="loading">Loading...</div>
  if (isError) return <div>Error! {JSON.stringify(error)}</div>

  return (
    <div className="container">
      {data &&
        data.pages.map((page) => {
          return (
            <React.Fragment key={page.nextId ?? 'lastPage'}>
              {page.posts.map((post: { id: number; title: string; createdAt: Date }) => (
                <div className="post" key={post.id}>
                  <p>{post.id}</p>
                  <p>{post.title}</p>
                  <p>{post.createdAt}</p>
                </div>
              ))}
            </React.Fragment>
          )
        })}

      {isFetchingNextPage ? <div className="loading">Loading...</div> : null}

      <span style={{ visibility: 'hidden' }} ref={ref}>
        intersection observer marker
      </span>
    </div>
  )
}

```
Let's understand what's happening here 

#### useInfiniteQuery

- It takes 3 arguments
- first is the unique key, which is required by react-query to use internally for caching and many other things
- A function that returns a `Promise` or throws an `Error` we usually fetch out data here
- This function also has access to an argument that has `2` properties namely `queryKey` which is the first argument of `useInfiniteQuery` and `pageParams` which is returned by `getNextPageParams` and initially its `undefined` hence we are setting its default value as an empty string
- Third argument has some options and one of them is `getNextPageParams` which should return some value that will be passed as `pageParams` to the next request 
- `isLoading` is a `boolean` that indicates the status of query on first load
- `isError` is a `boolean` which is `true` if there is any error thrown by the query function(second argument of `useInfiniteQuery`)
- `data` is the result of the successful request and contains `data.pages` which is the actual data from the request and `pageParams`
- `error` has the information about the error if there is any
- `isFetchingNextPage` is a `boolean` which can be used to know the fetching state of the request 
- `fetchNextPage` is the actual function that is responsible to fetch the data for the next page
- `hasNextPage` is a `boolean` that says if there is a next page to be fetched, always returns `true` until the return value from `getNextPageParams` is `undefnied`

#### useInView
- This is a hook by `react-intersection-observer` package which is created on top of the native `IntersectionObserver` API of javascript 
- it returns `2` values 
- Firstly, `ref` which should be passed to any DOM node we want to `observe`
- Secondly, `inView` which is a `boolean` that is `true` if the node that we set to `observe` is in the viewport 

Then we use a `useEffect` hook to check 2 conditions 
- If the `span` element which we passed the `ref` is in the viewport or not.
- If we have any data to fetch or not 

If both the conditions satisfy we then fetch the next page, that's it, this is all it takes to build an infinite scroll UI pattern


## Outro
I hope you found some value in the article, make sure you check the full code [here](https://github.com/sairaj2119/infinite-scroll-ui) as I did not include any code to style our beautiful posts 😂