Build a real-time document contribution list in NextJS
Tags: nextjs, javascript, react, tutorial
Published: 2022-07-12 22:03:26

This article discusses building a real-time document contribution list using Appwrite's Realtime service with Next.js, subscribing to channels in our database, and displaying a list of users contributing to our document when changes occur in the channels.

## GitHub URL

```
https://github.com/Tundesamson26/real-time-contribution-list
```

## Prerequisites
The following are required to follow along:

- Knowledge of JavaScript and React.js.
- Docker Desktop installed on your computer; run the `docker -v` command to verify your installation. If not, install it from the [Get Docker documentation](https://docs.docker.com/get-docker/)
- An Appwrite instance running on your 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 robust database and Realtime service to manage our application
- Experience with Next.js is advantageous but is not compulsory

## Setting up the 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, navigate to the preferred directory and run the terminal command below: 

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

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

```javascript
cd <name of our project>
npm run dev
```
To see our app, we then go to http://localhost:3000.

## Installing Dependencies
 
**Installing Avatar Generator**
[Avatar Generator](https://www.npmjs.com/package/random-avatar-generator) is a package that helps generate random avatars from a free online service for anyone to easily make a beautiful personal avatar!

To install random-avatar-generator in our project, we run the following terminal command.

```javascript
npm i random-avatar-generator
```
**Installing Tailwind CSS**

[Tailwind CSS](https://tailwindcss.com/) is a "utility-first" CSS framework allowing rapid creation of user interfaces for web applications. To install Tailwind CSS in our project, we run these terminal commands:

```javascript
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

These commands create two files in the root directory of our project, `tailwind.config.js` and `postcss.config.js`. Next, in our `tailwind.config.js`, we add the paths to all our template files with this code below.

```javascript
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```
We should add the `@tailwind` directives for Tailwind’s layers to our `./styles/globals.css` file.

```javascript
//globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
**Installing Appwrite**
[Appwrite](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) is an open-source, end-to-end, backend server solution that allows developers to build applications faster. To use Appwrite in our Next.js application, install the Appwrite client-side SDK by running the following command:

```javascript
npm install appwrite 
```
## Creating a new Appwrite project
Running a local Appwrite instance gives us access to the console. To create an account, we go to the local Appwrite instance on whatever port it is started on. Typically, this is on localhost:80 or is specified during Appwrite's installation.

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

![appwrite-console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhde28thvygfwfw4gcu6.png)

Our project dashboard appears once we have created the project. At the top of the page, click the **Settings** bar to access our **Project ID** and **API Endpoint**.
 
![appwrite-settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/56mtppzglykv26vtjmh9.png)
  
Now, we'll copy our **Project ID** and **API Endpoint**, which we need to initialize our Web SDK code.

In the root directory of our project, we create a `utils` folder, which will hold our `web-init.js` file. This file configures Appwrite in our application. Initialize the Web SDK in said file with:

```javascript

// Init your Web SDK
import { Appwrite } from "appwrite";
export const sdk = new Appwrite();

sdk
    .setEndpoint('http://localhost/v1') // Your Appwrite Endpoint
    .setProject('455x34dfkj') // Your project ID
;
```
## Creating a collection and attributes
Next, we'll set up our database to store our order status. In the Appwrite web [Console](http://localhost/console), click on **Database** on the left side of the dashboard.

![appwrite-database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dxgi1mbbndb5xp373k94.png)

Next, create a collection in the 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. We can modify the permissions to specify who has access to read or write to our database.

![appwrite-collection-level](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zd5723s2khhezcavhpdh.png)

On the right of the **Permissions** page, we copy the **Collection ID**, which we need to perform operations on the collection’s documents. Next, go to the **Attributes** tab to create the properties we want a document to have.

![appwrite-permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qpoypd5hvdzc0awzao86.png)
 
Now, create the string attribute of **username**, **avatar**, **document_id**.

![appwrite-attribute](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j15ne1hzmj05qx8106wx.png)

## Creating our document contribution application
Our document application will have a page with a navbar section to display the currently active users. This page will also subscribe to the document contribution list and displays its updates in real time. We create this document application with the GitHub [gist](https://gist.github.com/Tundesamson26/ccbf77369ce4c75433eb4d38a4d31501) below.

{% embed https://gist.github.com/Tundesamson26/fffc671fdf1395edcafb816f09899012 %}

In the `index.js`, we did the following:

- Imported required dependencies and components.
- Implemented state variables to store the avatars.
- Rendered the document contribution interface.

At this point, our application should look like this:
![Document-contribution-application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eue95qi1vuwwoxre4hw4.png)

**Creating an anonymous user session**
Appwrite requires a user to sign in before reading or writing to a database to enable safety in our application. However, we can create an anonymous session that we'll use in this project. We'll do so in our `web-init.js` file.

```javascript
// Init your Web SDK
import { Appwrite } from "appwrite";

export const sdk = new Appwrite();
sdk
  .setEndpoint("http://localhost/v1") // Your API Endpoint
  .setProject("yidfy733wnx"); // Your project ID
export const createAnonymousSession = async () => {
  try {
    await sdk.account.createAnonymousSession();
  } catch (err) {
    console.log(err);
  }
};
```
**Generating random avatar**
We need to generate a random avatar for each user that is currently active on our page using the Avatar Generator package we installed. First, import the dependency into the `pages/index.js` file.

```javascript
import { AvatarGenerator } from 'random-avatar-generator';
```

We then write a conditional statement to check for the currently active users on the mount of our application using the React `useEffect()` Hooks.

```javascript
useEffect(() => {
    if (!avatar) {
      const _avatar = localStorage.getItem("avatar") || AvatarGenerator();
      localStorage.setItem("avatar", _avatar);
      setAvatar(_avatar);
      console.log(_avatar);
    }
}, [avatar]);
```
## Add interaction with our database
When a user is active on our page, we should be able to see a list containing the active user's avatar. This will create a document for the new user, automatically update our database with avatar, and then delete when a user exists on the page.

**Creating database documents**
We need to create a document that stores our list of users' avatars in the viewers' attribute. 

In the `pages/index.js` file, we write an `addViewer()` function to create the document for new and active users.

```javascript
const addViewer = () => {
    console.log("Adding user");
    sdk.database.createDocument([collectionID], userData.username, {
      username: userData.username,
      avatar: userData.avatar,
      document_id: "test-document",
    });
};
```
This `addViewer()` function above does the following:

- The `createDocument()` method creates a document using the collection ID and data fields to be stored. This collection ID is the same ID we copied from our Permissions Page earlier.
- Passed the **username**, **avatar**, and **document_id** as parameters.

**Deleting database documents**
We need to delete the user document immediately after they leave the page. This will delete the user's avatar from the list of active users contributing to our document.

In the `pages/index.js` file, we write a `cleanup()` function to delete the user immediately after leaving our page using `addEventListener()` and `removeEventListener()` in the React `useEffect()` Hooks.

```javascript
useEffect(() => {
    const cleanup = () => {
      let promise = sdk.database.deleteDocument(
        [collectionID],
        userData.username
      );
      promise.then(
        function (response) {
          console.log(response); // Success
        },
        function (error) {
          console.log(error); // Failure
        }
      );
    };
    window.addEventListener("beforeunload", cleanup);
    return () => {
      window.removeEventListener("beforeunload", cleanup);
    };
 }, []);
```
This `cleanup()` function above does the following:

- Uses the `deleteDocument()` method to delete a document using the collection ID and username. This collection ID is the same ID we copied from our Permissions Page earlier.

**Subscribing to updates on the document**
When a new user is active on our document contribution, we should be able to see their avatar and delete their avatar when they leave our page. This can be broadcast to everyone as an event in Realtime using the `subscribe` method.

```javascript
useEffect(() => {
    // Subscribe to collection channel
    const _subscribe = sdk.subscribe(
      "collections.[collectionID].documents",
      (response) => {
        const { payload } = response;
        console.log(payload);
      }
    );
    return () => {
      _subscribe();
    };
}, []);
```
In the code block above, we do the following:

- Subscribe to a channel using Appwrite's `subscribe` method, which receives two parameters — the channel we subscribe to and a callback function — to understand more about the various channels we can subscribe to, check out Appwrite's Realtime Channels.

Next is to render the avatar to the application to see the avatar of the active user contributing to our document. In the `pages/index.js`, we render the user avatar to our application.

```react
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
      {viewers.length > 0
         ? viewers.map((user) => (
            <img
              className="h-8 w-8 rounded-full"
              src={user.avatar}
              alt=""
            />
         ))
      : null}   
 </div>
```
The above code snippet maps through our database's avatar displays on our document page. Below is our complete document contribution list page. Opening the page from multiple browsers creates a new document for each user and displays their avatars.

![Browser with multiple user avatars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oajidq5eyep9eed7bflu.png)

![New document for new user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0tejnho2d791a8v21dh.png)
 
## Conclusion

This article discussed Appwrite's Realtime feature to subscribe to application events and display a list of the avatars for the active users contributing to our document.

## Resources
- [Getting started with Appwrite for web](https://appwrite.io/docs/getting-started-for-web?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Random-avatar-generator](https://www.npmjs.com/package/random-avatar-generator)
- [What Appwrite is and how to use it Efficiently](https://appwrite.io/docs/getting-started-for-web?utm_source=hackmamba&utm_medium=hackmamba-blog)