Create a Robust Blog-Post-Reads Counter in Next.js
Published: 2022-05-03 17:58:26

Blog reads tells us how often our content has been read. They help us understand how much traffic our content has been getting and we can use this data to strategize and repurpose our content accordingly.

## What we will be building

This post will discuss using the `react-intersection-observer` library to implement a blog read counter that is incremented when a user has read 70 percent of our blog post. These blog reads are stored on a database. You don’t need a custom server to manage this.

**GitHub URL** 

https://github.com/Iheanacho-ai/appwrite-blog-read-counter
{% embed https://github.com/Iheanacho-ai/appwrite-blog-read-counter
%}

## Prerequisites 

To get the most out of this project, we require the following:

- A basic understanding of CSS, JavaScript, and React.jsDocker Desktop is installed on the computer, run the `docker -v` command to verify if we have docker desktop installed, if not, install it from [here](https://www.docker.com/products/docker-desktop/)
- An [Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba&utm_campaign=hackmamba-appwrite) instance running on our computer; check out [this article](https://dev.to/hackmamba/create-a-local-appwrite-instance-in-3-steps-19n9?utm_source=hackmamba&utm_medium=hackmamba) to create a local Appwrite instance. We will use Appwrite’s powerful database service and experience to manage our analytics data.


## Setting up our Next.js app

[Next.js](https://nextjs.org/) is an open-source React framework that enables us to build server-side rendered static web applications.

To create our Next.js app, we navigate to our preferred directory and run the terminal command below: 

```bash
    npx create-next-app@latest
    # or
    yarn create next-app
```

After creating our app, we change the directory to the project and start a development server with:

```bash
    cd <name of our project>
    npm run dev
```

To see our app, we go to [http://localhost:3000](http://localhost:3000).


## Installing react-intersection-observer

[React-intersection-observer](https://www.npmjs.com/package/react-intersection-observer) is a react library that allows developers to monitor when an element enters or leaves the viewport.

To install the react-intersection-observer in our application, run this terminal code.

```bash
    npm install react-intersection-observer --save
    
    #or
    
    yarn add react-intersection-observer
```

## Installing Appwrite

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba&utm_campaign=hackmamba-appwrite) is an open-source, end-to-end, back-end server solution that allows developers to build applications faster.

To use Appwrite in our Next.js application, we install the Appwrite client-side SDK for web applications.

```bash
    npm install appwrite
```

## Creating a new Appwrite project

During the creation of the Appwrite instance, we specified what hostname and port we see our console. The default value is [localhost:80](http://localhost/).

We go to [localhost:80](http://localhost/) and create a new account to see our console.

On our console, there is a **Create Project** ****button. Click on it to start a new project.

![Appwrite](https://paper-attachments.dropbox.com/s_314B50E78D2533608F0DEE9BE97FEF3C5B7916B2F8CAC55559F4DCA3A07D86F6_1648400276700_Screenshot+64.png)


Our project dashboard appears once we have created the project. At the top of the page, there is a **S****ettings** bar. Click it to access the **Project ID** and **API Endpoint**.  


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460489033_Screenshot+193-2.png)


We copy the **Project ID** and **API Endpoint**, which we need to initialize the Appwrite Web SDK.


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460517881_Screenshot+194.png)


In our project's root directory, we create a `utils.js` file to initialize our Web SDK.

```jsx
    import { Appwrite } from 'appwrite';
    // Init your Web SDK
    
    export const sdk = new Appwrite();
    sdk
    .setEndpoint('http://localhost/v1') // Your API Endpoint
    .setProject('625d9071b3fc17c7bfb6') // Your project ID
    ;
```

**Creating an anonymous user session**

[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba&utm_campaign=hackmamba-appwrite) requires a user to sign in before reading or writing to a database to enable safety in our application. However, they allow us to create an anonymous session for small projects like this.

We use a function to create an anonymous user session once this application mounts. We will run this `createAnoymousSession` function later in this tutorial.

```jsx
    import { Appwrite } from 'appwrite';
    // Init your Web SDK
    
    export const sdk = new Appwrite();
    sdk
    .setEndpoint('http://localhost/v1') // Your API Endpoint
    .setProject('625d9071b3fc17c7bfb6') // Your project ID
    ;
    
    //Creating anonymous Session
    export const createAnonymousSession = async() => {
        try{
            await sdk.account.createAnonymousSession();
        }catch(err){
            console.log(err)
        }    
    }
```

**Creating** **the** **collection and attributes**

In the Appwrite web [Console](http://localhost/console), we click on **Database** on the left side of the dashboard.


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460665585_Screenshot+206.png)


We create a collection in our database tab by clicking on the **Add Collection** button. This action redirects us to a **Permissions** page.

At the **Collection Level****,** we want to assign a **Read Access** and **Write Access** with a **role:all** ****value. You can modify these permissions to specify who has access to read or write to our database.


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460679536_Screenshot+207.png)


Next, we go to our **Attributes** tab to create the properties we want a document to have. 


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460713052_Screenshot+195.png)


We create an integer attribute of **blogCount.**

![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460817678_Screenshot+199.png)

## Creating an Appwrite Document

After creating the attributes our document will have, we go to the **Documents** section of the **Database.**


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650460848758_Screenshot+197.png)


Next, we click on the **Add Document** button and input a zero value to create a document in our collection. This value represents the blog reads.


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650461129369_Screenshot+209.png)


On the right of our newly created document, we copy our **Collection ID** and **Document ID**, which we need to perform functions on this document.


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650461152978_Screenshot+210.png)

## Creating the Blog Post.

In our `index.jsx` file, we create a sample blog post that we will use to measure and store our blog reads.

{% embed https://gist.github.com/Iheanacho-ai/6852e1cd43e37fe457a45ae702a109ba %}


[https://gist.github.com/Iheanacho-ai/6852e1cd43e37fe457a45ae702a109ba](https://gist.github.com/Iheanacho-ai/6852e1cd43e37fe457a45ae702a109ba)

Next, we elevate our blog post user interface by adding styles in the `global.css` file.


{% embed https://gist.github.com/Iheanacho-ai/c35dc718ccbd62cc0339a4a18e5ee3b2 %}


[https://gist.github.com/Iheanacho-ai/c35dc718ccbd62cc0339a4a18e5ee3b2](https://gist.github.com/Iheanacho-ai/c35dc718ccbd62cc0339a4a18e5ee3b2)

Here is how our blog post looks.


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650461489260_Screenshot+212.png)


## Utilizing React-intersection-observer to count blog reads

The react-intersection-observer library allows us to run a function or perform a task when a part of the website is visible in the viewport. We want to increase our blog count reads when a user has read 70 percent of the blog post with this logic.


```jsx
    import {useEffect, useState} from 'react';
    import { useInView } from 'react-intersection-observer';
    
    const Home = () => {
      const { ref: myRef, inView } = useInView();
      const [appBlogRead, setAppBlogRead] = useState(0)
      const [blogCount, setBlogCount] = useState()
      
      return (
          <div className="blog">
            {/* The rest of the app goes here */}
              ...
          </div>
        )
      }
    
    export default Home;
```

In the code block above, we do the following:

- Import the **react-intersection-observer** `useView` hook in our `index.js` file
- Destructure the `useView` hook to get a `ref` that we assign to the element we want to monitor and an `inView` status to tell us if the element is present on the screen
- Create two state variables, an `appBlogRead` variable to hold how many times the blog has been read per mount, which will not be more than one, and a `blogCount` variable to store how many times our blog has been read in general, we will update this variable with the value from our database

Next, in our `index.jsx` file, we pass our `blogCount` variable to the `h3` element to display our blog reads.

```jsx
    <h3>Blog reads: {blogCount}</h3>
```

Understanding that we only want a read to count when a user has gone through 70 percent of the page, we add the `ref` to an element in the last quarter of our blog post. Doing this ensures that a read will not count unless that element has been on screen.

```jsx
    <div className="blog">
      <div className="nav"></div>
      <div className="blog-container">
        <div className="writer">
          <div className="writer-image"></div>
          <div className="writer-name">Adut Elsesser</div>
        </div>
        <div className="blog-header">
          <h3>Blog reads: {blogCount}</h3>
          <h2>Mind on the road, your dilated eyes</h2>
          <p>Watch the clouds float, white Ferrari</p>
        </div>
        <div className="blog-image"></div>
        <div className="blog-story-container">
          <p>
            Had a good time
            (Sweet 16, how was I supposed to know anything?I let you out at CentralI didn't care to state the plainKept my mouth closedWe're both so familiarWhite Ferrari, good times
          </p>
          <p> 
            Stick by me, close by me You were fine You were fine here That's just a slow body You left when I forgot to speak So I text to speech, lesser speeds Texas speed, yes Basic takes its toll on me, Eventually, eventually, yes Ah, on me eventually, eventually, yes I care for you still and I will forever That was my part of the deal, honest We got so familiar Spending each day of the year, White Ferrari Good times In this life, life In this life, life
    
          </p>
    
          {/* The ref */}
    
          <p ref={myRef}> 
            One too many years Some tattooed eyelids on a facelift Mind over matter is magic I do magic If you think about it it'll be over in no time And that's life I'm sure we're taller in another dimension You say we're small and not worth the mention
          </p>
          <p>
            You're tired of movin', your body's achin' We could vacay, there's places to go Clearly this isn't all that there is Can't take what's been given But we're so okay here, we're doing fine Primal and naked You dream of walls that hold us imprisoned It's just a skull, least that's what they call it And we're free to roam
          </p>
        </div>
      </div>
    </div>
```

Next, we use the `useEffect` hook to check if the element we are monitoring is in view and if our `appBlogRead` variable is less than one. If these statements are true, we want to add a read count to our `appBlogRead` and `blogCount` variable.  

```jsx
    import {useEffect, useState} from 'react';
    import { useInView } from 'react-intersection-observer';
    import {sdk, createAnonymousSession } from '../utils.js'
    
    const Home = () => {
      const { ref: myRef, inView } = useInView();
      const [appBlogRead, setAppBlogRead] = useState(0)
      const [blogCount, setBlogCount] = useState()
    
      useEffect(() => {
        if(inView){
          if (appBlogRead < 1) {
            setAppBlogRead(appBlogRead + 1)
            setBlogCount(blogCount + 1)
          }else{
            return
          }
          
        }
      }, [inView])
    
      return(
          <div></div>
      )
    }
    
    export default Home;
```   

## Storing the view count to the database

In the `index.js` file, we create two functions to update and get information from our database.

```jsx
    const handleBlogCount = async () => {
        try {
          let promise =  await sdk.database.listDocuments('collectionID')
          setBlogCount(promise.documents[0].blogCount)
          
        } catch (error) {
          console.log(error)
        }
    }
    
    const updateBlogCount = async () => {
      try {
          await sdk.database.updateDocument('collectionID', 'documentID', {
            "blogCount": blogCount
          })
          handleBlogCount()
          
        } catch (error) {
          console.log(error)
        }
    }
    
    useEffect(() => {
      createAnonymousSession()
      handleBlogCount()
    }, [])
    
      useEffect(() => {
        updateBlogCount()
      }, [blogCount])
```    

In the code block above, we do the following.

- Create a `handleBlogCount` function that finds a collection using the collection ID parameter, this function updates the `blogCount` variable using its response
- Create a `updateBlogCount` function that finds a document using its collection ID and document ID parameters,`updateBlogCount` function then updates the document using the `blogCount` variable, these collection and document ID are from the document we created at the start of this tutorial
- Use the React `useEffect` to fire off the `createAnonymousSession` in our `utils.js` file and the `handleBlogCount` function once our application mounts
- Runs our `updateBlogCount` function each time our `blogCount` variable is updated using React `useEffect` hook

We have created a robust blog post read counter in Next.js with this.


{% embed https://gist.github.com/Iheanacho-ai/d1eb08bea25f99d406e185a077a0c900 %}


[https://gist.github.com/Iheanacho-ai/d1eb08bea25f99d406e185a077a0c900](https://gist.github.com/Iheanacho-ai/d1eb08bea25f99d406e185a077a0c900)

Here is how our blog post looks...


![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650461522497_Screenshot+201.png)


Read about 70 percent of the blog post to see the blog post read increase on the page and in the database.

![Appwrite](https://paper-attachments.dropbox.com/s_007C86FEAF59972CE75C37903AE26328081A370FBE5805C3C9222C70AD158CE2_1650728356375_blog-counter-giphy.gif)

## Conclusion

This article discussed tracking blog reads with the react-intersection observer library, storing and retrieving these reads using an [Appwrite](htpps://appwrite.io/?utm_source=hackmamba&utm_campaign=hackmamba) database. As the post creates the base for an analytics implementation, update the code to restrict the blog count to only one read per user session. 


## Resources

Here are some resources that might be helpful:

- [react-intersection-observer](https://www.npmjs.com/package/react-intersection-observer?activeTab=readme)
- [Getting started with Appwrite for web](https://appwrite.io/docs/getting-started-for-web?utm_source=hackmamba&utm_campaign=hackmamba)
- [Database API](http://- Database API)