# Build a Decentralized Social Network with Reactjs, TailwindCSS & Lens Protocol

One of the most important advantages of social networks is the potential for real human connections. Websites and applications that emphasize collaboration, sharing of content, engagement, and community-based feedback are collectively referred to as social media.

Web3 social appears to have enormous potential for consumers, leveraging [Lens Protocol](https://docs.lens.xyz/docs/what-is-lens) to restore faith in what social media may be while also giving us the power to manage how our content is utilized.

This post will teach us how to build a decentralized social network with Reactjs, Tailwindcss, and Lens protocol.
 
Live Project: [Decentralized Social Network](https://decentralize-social-media-app-with-lens-protocol.vercel.app/)

GitHub Repository: [Decentralized Social Network GitHub](https://github.com/Olanetsoft/decentralize-social-media-app-with-lens-protocol)

## Prerequisite

Make sure to have Node.js or npm installed on your computer. If you don't, click [here](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).

## Project Setup and Installation

To quickly get started with the project setup and installation, we will clone this [project on GitHub](https://github.com/Olanetsoft/decentralize-social-media-app-with-lens-protocol/tree/starter) and ensure we are on the `starter` branch.

Next, we will launch the project locally after cloning it using the following command on our terminal.

Project installation using `yarn.`

```
cd decentralize-social-media-app-with-lens-protocol && yarn && yarn start
```

Project installation using `npm.`

```
cd decentralize-social-media-app-with-lens-protocol && npm install && npm start
```

After cloning and installing the project, we should have something similar to what we have below:

![Build a Decentralized Social Network with Reactjs, TailwindCSS & Lens Protocol](https://cdn.hashnode.com/res/hashnode/image/upload/v1658270966617/hoCP4Bz-A.png align="left")

Before implementing all profile retrieval, let's discuss the Lens Protocol in the following section.

## What is Lens Protocol?

The [Lens Protocol](https://lens.xyz) is a smart contract-based social graph on the Polygon Proof-of-Stake blockchain. It enables developers to build composable, user-owned social graphs by allowing them to own the links between themselves and their community. 

Learn [more](https://mirror.xyz/lensprotocol.eth/YG9iFIs2emVFRtj3JqY95Dk6opNqM0aC9YoM-Ppp5as) about Lens protocol.

## Building Social Network dApp

In this section, we will build a decentralized social network that implements all profile and single profile retrieval and publications.

### Fetching all Profiles

After cloning the repository successfully, we can now build the feature to fetch all profiles using the Lens protocol API.

To set up a graphQL call to the Lens protocol API, we can now go to the `api.js` file inside the `pages/api/` directory and edit it with the following code snippet.

```
import { createClient } from "urql";

const API_URL = "https://api.lens.dev";

// Setup the client to use the API_URL as the base URL
export const client = createClient({
  url: API_URL,
});

// Get the recommended profiles
export const getProfiles = `
  query RecommendedProfiles {
	recommendedProfiles {
	      id
	    name
	    bio
	    attributes {
	      displayType
	      traitType
	      key
	      value
	    }
	      followNftAddress
	    metadata
	    isDefault
	    picture {
	      ... on NftImage {
		contractAddress
		tokenId
		uri
		verified
	      }
	      ... on MediaSet {
		original {
		  url
		  mimeType
		}
	      }
	      __typename
	    }
	    handle
	    coverPicture {
	      ... on NftImage {
		contractAddress
		tokenId
		uri
		verified
	      }
	      ... on MediaSet {
		original {
		  url
		  mimeType
		}
	      }
	      __typename
	    }
	    ownedBy
	    dispatcher {
	      address
	      canUseRelay
	    }
	    stats {
	      totalFollowers
	      totalFollowing
	      totalPosts
	      totalComments
	      totalMirrors
	      totalPublications
	      totalCollects
	    }
	    followModule {
	      ... on FeeFollowModuleSettings {
		type
		amount {
		  asset {
		    symbol
		    name
		    decimals
		    address
		  }
		  value
		}
		recipient
	      }
	      ... on ProfileFollowModuleSettings {
	       type
	      }
	      ... on RevertFollowModuleSettings {
	       type
	      }
	    }
	}
      }
`;

```

In the code snippet above, we:

- Imported a `createClient` from `urql` to help send GraphQL requests to our API
- Set up a client making a request to Lens protocol domain
- Setup the `getProfiles`endpoint to help retrieve recommended profiles. To learn more, check the official Lens protocol [documentation](https://docs.lens.xyz/docs/recommended-profiles)

Next, we will import the `client` and `getProfiles` in the `index.js` file and update the file with the following code snippet.

```
import Head from "next/head";
import Profiles from "../components/profiles";

import { useState, useEffect } from "react";

import { client, getProfiles } from "../pages/api/api";

export default function Home() {

  // Setup the state for the profiles
  const [profiles, setProfiles] = useState([]);

  // Get the recommended profiles
  const fetchProfiles = async () => {
    try {
      const response = await client.query(getProfiles).toPromise();

      setProfiles(response.data.recommendedProfiles);

      console.log(response.data.recommendedProfiles);
    } catch (e) {
      console.log(e);
    }
  };

  // Run the fetchProfiles function when the component is mounted
  useEffect(() => {
    fetchProfiles();
  }, []);

  // Render the component
  return (
    <div className="grid grid-cols-3 divide-x">
      <Head>
        <title>Decentralize Social Media App - Lens protocol</title>
        <meta name="description" content="Decentralize Social Media App" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div className="col-span-3">
        <div className="px-4 py-8">
          <h1 className="text-3xl font-bold leading-tight text-center">
            Decentralize Social Media App - Lens protocol
          </h1>
          <p className="text-center">
            This is a decentralized social media app built on the Lens protocol.
          </p>
        </div>
      </div>
      {profiles && profiles.length > 0 ? (
        <Profiles profiles={profiles} />
      ) : (
        <div className="text-center text-gray-500 p-5 font-medium text-xl tracking-tight leading-tight">
          <p>Loading...</p>
        </div>
      )}
    </div>
  );
}
```

In the code snippet above, we:

- Created a state variable `profiles` to save the profile retrieved from the API request
- Created a function `fetchProfiles` to get recommended profiles from the API using the client we created earlier and the recommended profile request endpoint
- Reference the `fetchProfiles` function when the component is mounted
- Validated if the `profiles` variable length is greater than `0`, we then render the `Profiles` component we imported in the earlier step; otherwise, we return the `Loading...` while the API request is in progress

At the moment, the `Profiles` component is empty. Let us work on that in the next step.

Inside the `profiles.js` file in the `components` directory, we will update it with the following code snippet to iterate over profile records and retrieve and render individual profiles in a card layout.

```
import React from "react";
import Image from "next/image";
import Link from "next/link";

export default function Profiles({ profiles }) {
  return (
    <>
      {profiles.length > 0 &&
        profiles.map((profile, index) => (
          <Link href={`/profile/${profile.id}`} key={index}>
            <div className="max-w-md rounded-lg shadow-lg bg-white mt-5 mb-5 p-5 border border-radius-8 cursor-pointer hover:bg-gray-100 hover:shadow-lg ml-8">
              <div className="flex flex-shrink-0 p-4 pb-0">
                <div className="flex items-center">
                  <div>
                    {profile &&
                    profile.picture &&
                    profile.picture.original &&
                    profile.picture.original.url ? (
                      <Image
                        src={`${profile.picture.original.url}`}
                        alt={profile.name}
                        width={64}
                        height={64}
                        className="rounded-full"
                      />
                    ) : (
                      <div className="rounded-full bg-gray-500 h-12 w-12"></div>
                    )}
                  </div>
                  <div className="ml-3">
                    <p className="text-base leading-6 font-medium ">
                      {profile.name}{" "}
                      <span className="text-sm leading-5 font-medium text-gray-500 group-hover:text-gray-400 transition ease-in-out duration-150">
                        @{profile.handle}
                      </span>
                    </p>
                  </div>
                </div>
              </div>
              <div className="pl-16">
                <p className="text-base width-auto font-small flex-shrink">
                  {profile.bio ? profile.bio : "No bio available 😢"}
                </p>
              </div>
            </div>
          </Link>
        ))}
    </>
  );
}

```

Next, we will start the server in development mode using the command below;

```
yarn dev
```

When we open our browser, we should see something similar to what is shown below.
 
![Decentralize Social Media App - Lens protocol.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1658355541275/E9swpZynR.gif align="left")


### Implementing Single Profile Functionality

Using the Lens protocol API, we will create a single profile retrieval functionality in this section. As we may have seen, when we click on any card, we see a `404 page,` which means the route does not exist, but let's rectify that immediately.

Let's update the `api.js` in the `pages/api/` directory with the following code snippet.

```
//...

export const getProfile = `
  query Profiles($id: ProfileId!) {
	profiles(request: { profileIds: [$id], limit: 25 }) {
	  items {
	    id
	    name
	    bio
	    attributes {
	      displayType
	      traitType
	      key
	      value
	    }
	    metadata
	    isDefault
	    picture {
	      ... on NftImage {
		contractAddress
		tokenId
		uri
		verified
	      }
	      ... on MediaSet {
		original {
		  url
		  mimeType
		}
	      }
	      __typename
	    }
	    handle
	    coverPicture {
	      ... on NftImage {
		contractAddress
		tokenId
		uri
		verified
	      }
	      ... on MediaSet {
		original {
		  url
		  mimeType
		}
	      }
	      __typename
	    }
	    ownedBy
	    dispatcher {
	      address
	      canUseRelay
	    }
	    stats {
	      totalFollowers
	      totalFollowing
	      totalPosts
	      totalComments
	      totalMirrors
	      totalPublications
	      totalCollects
	    }
	  }
	  pageInfo {
	    prev
	    next
	    totalCount
	  }
	}
      }
`;

```

In the code snippet above, we added the endpoint to get a single profile using the provided by the Lens protocol in their [docs](https://docs.lens.xyz/docs/get-profiles).

Next, we will update the `[id].js` file in the `profile` folder under the `pages` directory with the following code snippet.

%[https://gist.github.com/Olanetsoft/aab0ccb3f0f060e6c92769705951c5be]

In this code snippet above, we:

- Imported `client` and `getProfile` from the `api.js` file
- Created a state variable `profile` to save the profile retrieved
- Used the `useRouter` hook to access the profile `id.` 
- Created `fetchProfile` to retrieve a single profile using the client and API endpoint 
- Fetched the profile when the component is mounted using the `useEffect` hook
- Rendered individual data on the UI returned

Let's head to our browser to view what we have. We should have something similar to what is shown below.

![Build a Decentralized Social Network with Reactjs, TailwindCSS & Lens Protocol](https://cdn.hashnode.com/res/hashnode/image/upload/v1658359043855/_V9usK9ak.png align="left")

As highlighted in the image above, those are dynamic data retrieved from the API associated with the account.

Next, we will implement fetching profile publications in the following section.

### Fetching Profile Publications

A profile's posts, comments, and mirrors are considered publications. Learn more about the publication's endpoints [here](https://docs.lens.xyz/docs/publication-1).

Let's update the `api.js` in the `pages/api/` directory with the following code snippet.

```
//...

export const getPublications = `
  query Publications($id: ProfileId!, $limit: LimitScalar) {
    publications(request: {
      profileId: $id,
      publicationTypes: [POST],
      limit: $limit
    }) {
      items {
        __typename 
        ... on Post {
          ...PostFields
        }
      }
    }
  }
  fragment PostFields on Post {
    id
    metadata {
      ...MetadataOutputFields
    }
    createdAt
  }
  fragment MetadataOutputFields on MetadataOutput {
    name
    description
    content
    media {
      original {
        ...MediaFields
      }
    }
    attributes {
      displayType
      traitType
      value
    }
  }
  fragment MediaFields on Media {
    url
    mimeType
  }
`;
```

Next, we will update the `[id].js` file in the `profile` folder under the `pages` directory with the following code snippet to retrieve profile publications.

```
//...

import { client, getProfile, getPublications } from "../api/api";

export default function Profile() {
  
//...

const [publications, setPublications] = useState();


  async function getProfilePublications() {
    try {
      const returnedPublications = await client
        .query(getPublications, { id, limit: 10 })
        .toPromise();

      const publicationsData = returnedPublications.data.publications.items;

      console.log("publicationsData", publicationsData);

      setPublications(publicationsData);
    } catch (err) {
      console.log("error fetching publications...", err);
    }
  }

  useEffect(() => {
    if (id) {

      //...

      getProfilePublications();
    }
  }, [id]);

  return (
    <div>
      
      //...

      <div className="container mx-auto flex flex-col lg:flex-row mt-3 text-sm leading-normal">

     //...

        <div className="w-full lg:w-1/2 bg-white mb-4">
          <div className="p-3 text-lg font-bold border-b border-solid border-grey-light">
            Publications
          </div>
          {publications &&
            publications.map((publication) => (
              <div key={publication.id}>
                <div className="flex border-b border-solid border-grey-light">
                  <div className="w-1/8 text-right pl-3 pt-3">
                    <div>
                      <i className="fa fa-thumb-tack text-teal mr-2"></i>
                    </div>
                    <div>
                      <a href="#">
                        {profile &&
                        profile.picture &&
                        profile.picture.original &&
                        profile.picture.original.url ? (
                          <Image
                            src={profile.picture.original.url}
                            alt="avatar"
                            className="rounded-full h-12 w-12 mr-2"
                            width={50}
                            height={50}
                          />
                        ) : (
                          <div className="rounded-full bg-gray-500 h-12 w-12"></div>
                        )}
                      </a>
                    </div>
                  </div>
                  <div className="w-7/8 p-3 pl-0 ml-4">
                    <div className="flex justify-between">
                      <div>
                        <span className="font-bold">
                          <a href="#" className="text-black">
                            {profile && profile.name}
                          </a>
                        </span>
                        <span className="text-grey-dark">
                          @{profile && profile.handle}
                        </span>
                        <span className="text-grey-dark">&middot;</span>
                      </div>
                    </div>

                    <div className="mb-4">
                      <p className="mb-6 text-grey-dark mt-2">
                        {publication.metadata.content}
                      </p>
                      <p>
                        <a href="#">
                          {publication.metadata.media > 0 &&
                          publication.metadata.media[0].original.mimetype ===
                            "video/mp4" ? (
                            <video
                              controls
                              style={{ width: "700", height: "400" }}
                            >
                              <source
                                src={publication.metadata.media[0].original.url}
                                type="video/mp4"
                              />
                            </video>
                          ) : null}
                        </a>
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            ))}
        </div>
   
      //...

    </div>
    </div>
  );
}

```

In this code snippet above, we:

- Imported `getPublications` from the `api.js` file
- Created a state variable `publications` to save the publications retrieved
- Created `getProfilePublications` to retrieve profile publication using the client and API endpoint 
- Fetched the profile publications when the component is mounted using the `useEffect` hook
- Rendered individual profile publication on the UI returned

Next, we can test our application by navigating to our browser.


![Build a Decentralized Social Network with Reactjs, TailwindCSS & Lens Protocol](https://cdn.hashnode.com/res/hashnode/image/upload/v1658360787839/G96FLOsOl.gif align="left")

## What Next?

This post covers a few endpoints provided by Lens protocol. We can always play around with endpoints and functionality like Follow, Create Profile, Comment, Mirror, etc.

I strongly advise looking over the Lens [API documentation](https://docs.lens.xyz/docs) and the Lens [apps](https://lens.xyz/#apps) for some ideas.


## Conclusion

This post teaches us how to build a decentralized social network with Reactjs, TailwindCSS & Lens protocol.


## References

- [Lens Protocol Documentation](https://docs.lens.xyz/docs)
- [Full Stack Web3 with Lens Protocol, Next.js, and GraphQL](https://www.youtube.com/watch?v=LcxOdWWL8xs)
- [Web3 Social](https://eda.hashnode.dev/web3-social-and-building-withlens-protocol) 
- [Getting Started With Lens Protocol As A Frontend Developer](https://ankr.hashnode.dev/getting-started-with-lens-protocol-as-a-frontend-developer)


I'd love to connect with you at [Twitter](https://twitter.com/olanetsoft) | [LinkedIn](https://www.linkedin.com/in/olubisi-idris-ayinde-05727b17a/) | [GitHub](https://github.com/Olanetsoft) | [Portfolio](https://idrisolubisi.com/)

See you in my next blog article. Take care!!!
