Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity
Tags: web3, blockchain, javascript, react
Published: 2022-04-27 15:07:34

Decentralized applications (dApps) are one of the most promising applications of blockchain technology. They open up new possibilities for consumer and business-focused products with never-before-seen capabilities.

It's fascinating to see how powerful decentralized applications may be built to supplement the commercial environment.

This post will teach us how to Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS, and Solidity.

It will be a platform where anyone on the internet can read, share, and post news, with the data being stored on the Polygon network's blockchain using smart contracts.

A smart contract is code stored on the blockchain and can be read and written from; we'll get into more detail later.

We'll build and deploy the smart contract and a website that allows people to connect their wallets and interact with our smart contract.


👉 GitHub Repositories 
- [Frontend](https://github.com/Olanetsoft/newsfeed-fe)
- [Smart Crontract](https://github.com/Olanetsoft/newsfeed-be)

## Prerequisite

Let us ensure we have Node/NPM installed on our PC. If we don't have it installed, head over [here](https://hardhat.org/tutorial/setting-up-the-environment.html) for a guide.


## Project Setup and Installation

Let's navigate to the terminal. We'll need to `cd` into any directory of our choice and then run the following commands:

```bash
mkdir newsfeed-be
cd newsfeed-be
npm init -y
npm install --save-dev hardhat
```

Let's get a sample project by running the command below:

```bash
npx hardhat
```

We'll go with the following options:

- A sample project. 
- Accept all other requests. 

Installing `hardhat-waffle` and `hardhat-ethers` is required for the sample project.

Just in case it didn't install automatically, we will install this other requirement with the following command:

```bash
npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers @openzeppelin/contracts

```

Next, we will install `@openzeppelin/contracts` for the counter we will use later in this tutorial.

```bash
npm i @openzeppelin/contracts
```

To make sure everything is working, let us run the command below.

```bash
npx hardhat test
```

We will see a passed test result in our console.

It is now possible for us to delete `sample-test.js` from the test folder and delete `sample-script.js` from the `scripts` directory. After that, go to contracts and delete `Greeter.sol.`

> The folders themselves should not be deleted!

We'll create a `NewsFeed.sol` file inside the contracts directory. When using Hardhat, file layout is crucial, so pay attention! We're going to start with the basic structure of every contract.

```js
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "hardhat/console.sol";

contract NewsFeed {

     constructor() {
        console.log("NewsFeed deployed");
    }
}
```

To build and deploy our smart contract, we will navigate to the `scripts` folder, create a new `run.js` file, and update it with the following code snippet:

```js
const main = async () => {
  // This will actually compile our contract and generate the necessary files we need to work with our contract under the artifacts directory.
  const newsFeedContractFactory = await hre.ethers.getContractFactory(
    "NewsFeed"
  );
  const newsFeedContract = await newsFeedContractFactory.deploy();

  await newsFeedContract.deployed(); // We'll wait until our contract is officially deployed to our local blockchain! Our constructor runs when we deploy.

  console.log("NewsFeed Contract deployed to: ", newsFeedContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

```

Let's run it with the following command.

```bash
npx hardhat run scripts/run.js
```
You should see something similar to what we have below:

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650905139543/13W0KVRSw.png)

We have a working smart contract 🥳 Let us deploy it to a local network.

Under the `scripts` folder, we will create a `deploy.js` file. Add the following code snippet.

```js
const main = async () => {
  const [deployer] = await hre.ethers.getSigners();
  const accountBalance = await deployer.getBalance();

  console.log("Deploying contracts with account: ", deployer.address);
  console.log("Account balance: ", accountBalance.toString());

  const Token = await hre.ethers.getContractFactory("NewsFeed");
  const portal = await Token.deploy();
  await portal.deployed();

  console.log("NewsFeed address: ", portal.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

runMain();
```

Before deploying, let us ensure we have our local node up and running in a separate terminal with the following command.

```bash
npx hardhat node
```

Next, we will deploy our smart contract.

```bash
npx hardhat run scripts/deploy.js --network localhost
```

We should have something like this.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650905919758/C0KqLAbCt.png)

## Building and Deploying NewsFeed Smart Contract to Blockchain

Everything, including the test script and the `deploy.js` file, is in place. We'll update the smart contract, `run.js,` and `deploy.js` files with the following code snippet:

Updating the `contracts/NewsFeed.sol` file.

{% gist https://gist.github.com/Olanetsoft/5d0d95bc7a7467469cab9384366a0100 %}

Update `scripts/run.js`

```js
const main = async () => {
  // This will actually compile our contract and generate the necessary files we need to work with our contract under the artifacts directory.
  const newsFeedContractFactory = await hre.ethers.getContractFactory(
    "NewsFeed"
  );
  const newsFeedContract = await newsFeedContractFactory.deploy();

  await newsFeedContract.deployed(); // We'll wait until our contract is officially deployed to our local blockchain! Our constructor runs when we deploy.

  console.log("NewsFeed Contract deployed to: ", newsFeedContract.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

runMain();

```

`scripts/deploy.js`

```js
const main = async () => {
  const [deployer] = await hre.ethers.getSigners();
  const accountBalance = await deployer.getBalance();

  console.log("Deploying contracts with account: ", deployer.address);
  console.log("Account balance: ", accountBalance.toString());

  const Token = await hre.ethers.getContractFactory("NewsFeed");
  const portal = await Token.deploy();
  await portal.deployed();

  console.log("NewsFeed address: ", portal.address);
};

const runMain = async () => {
  try {
    await main();
    process.exit(0);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
};

runMain();

```

It's finally time to get down to business and deploy to the blockchain.

Before deploying to the blockchain, we'll need to create an [Alchemy](https://alchemy.com/?r=37c4f8545ef30da7) account.

Alchemy enables us to broadcast our contract creation transaction so that miners can pick it up as quickly as feasible. Once mined, the transaction is published as a valid transaction to the blockchain. After that, everyone's blockchain copy is updated.

After you sign up, we'll create an app like the one below. Remember to switch the network to Mumbai, where we'll be deploying.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650908442233/8LtHqwB-E.png)

We will need to grab our keys, as shown below, and store them for later use:

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650908800914/KCXOaGs6Z.png)

We'll need some MATIC tokens in our testnet account, and we'll have to request some from the network. Polygon Mumbai can get some phony MATIC by using a faucet. This fake MATIC can only be used on this testnet.

We can grab some MATIC token [here](https://faucet.polygon.technology/)

Let us update the `hardhat.config.js` file in the root project directory.

```js
require("@nomiclabs/hardhat-waffle");
require("dotenv").config();

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.4",
  networks: {
    mumbai: {
      url: process.env.STAGING_ALCHEMY_KEY,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};

```

If we look at the code snippet above, we can see that some keys were read from the `.env` file, as well as the import at the top of `require("dotenv").config(),` which implies we'll need to install the `dotenv` package and also create a `.env` file using the command below:

```bash
npm install -D dotenv

touch .env
```

Inside the `.env` file, we will add the following keys:

```bash
STAGING_ALCHEMY_KEY= // Add the key we copied from the Alchemy dashboard here
PRIVATE_KEY= // Add your account private key here
```

Getting our private account key is easy. Check out this [post](https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key).

Next, let's write a basic test to test out the most critical functions we'll be using.

To do so, open we will create a `feed-test.js` file inside the test directory and update it with the following code:

{% gist https://gist.github.com/Olanetsoft/23424ab459cfd10b02187a816d4ea60e %}

Next, we will run the test with the following command:

```bash
npx hardhat test
```

Now we can run the command to deploy our contract to a real blockchain network.

```bash
npx hardhat run scripts/deploy.js --network mumbai
```

Our output should look like what we have below.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650910133444/0iWWXMZN7.png)

We just deployed our contract. 🥳🥳🥳

## Building Frontend React Client

To quickly get started with the project setup and installation, we will clone this [project on GitHub](https://github.com/Olanetsoft/newsfeed-fe/tree/project-setup) and ensure we are on the `project-setup` branch. 

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

```bash
cd newsfeed-fe && yarn && yarn start
```

Or

```bash
cd newsfeed-fe && npm install && npm start
```

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

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650922180989/hGF64FrfX.png)

We want to get all the news feeds from the smart contract we just launched without requiring users to connect their wallets. This implies that anyone can use our app to browse information without linking their wallets and only connect wallets when they wish to create a news feed.

Let us update the `getContract.js` file inside the utilities folder with the following code snippet.

```js
import ContractAbi from "./newsFeed.json";
import { ethers } from "ethers";

export default function getContract() {
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner(
    "0x2C08B4B909F02EA5D8A0E44986720D76BAC8224B" // Random (fake) wallet address
  );
  let contract = new ethers.Contract(
    "0x545ed82953b300ae5a8b21339c942788599Cd239", // Our contract adress
    ContractAbi.abi,
    signer
  );
  return contract;
}
```

In the code snippet above, we get our contract and include a random wallet address in the `getSigner` function. This is because we want everyone that visits our site to reads news without having to connect their wallets.

> Ensure passing a valid ethereum wallet address when creating/saving a record on the blockchain

We also added a contract address displayed in our terminal when we deployed our contract to the blockchain.

Let's go back to the smart contract project we worked on before, then navigate to `artifacts/contracts/NewsFeed.json` and copy the entire content inside it. We will update the newsfeed.json file in the `utilities` folder with what we copied.

### Building the FeedList Component

In this section, we will create a `FeedList.js` file inside the `component` folder and update it with the following code snippet.

{% gist https://gist.github.com/Olanetsoft/d0270f1bba5ded06903b83b21f072979 %}

Next, we will import the `FeedList` component, the toast response, and `ToastContainer` by updating the `HomePage.js` file with the following code snippet.

{% gist https://gist.github.com/Olanetsoft/a212f4850fba9ea9c6de1230581086de %}

Because no record has yet been recorded on the blockchain, and we are yet to create the function that retrieves all the feeds made, we should have something similar to what is displayed above, which appears empty.

### Building User's Connect Wallet Functionality

This section will build the functionality that allows users to contact their wallets on our platform to create a feed.

Let's update the `HomePage.js` with the following code snippet.

{% gist https://gist.github.com/Olanetsoft/c7a9fa4b737aef275b5e72ed803cb126 %}

Next, we will update the `Header.js` file.

{% gist https://gist.github.com/Olanetsoft/d6715d39901aad03d1b1c7fa302075b7 %}

Clicking on the `Connect your Wallet` button, we will get a metamask login popup.


![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650926005127/6GJaf6-ct.png)

After connecting, we will be redirected back to our application where the button showing `Connect your wallet` earlier now shows `Create a Feed` as shown below.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650926154838/vqRHsv9J_.png)

### Building Upload News Feed Page

We will build a page where users can enter new feed details and upload them to the blockchain. Let us create `UploadPage.js` inside the `src` directory and update it with the following code snippet.

{% gist https://gist.github.com/Olanetsoft/270a02d0ed6d0745afa0859e169ed110 %}

Next, we will update the `App.js` file by importing the new page we just created with the following code snippet.

```js
//...

function App() {
  return (
    <Routes>
      //...
      <Route path="/upload" element={<Upload />} />
    </Routes>
  );
}

export default App;
```

Clicking on the `Create a New Feed` button on the homepage will redirect us to the upload page, as shown below.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650926783482/t6yClZdFk.png)

After entering all the required details for upload, we can proceed to submit the feed.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650928408906/048uH7Ont.png)

---
![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650928428817/gvG6vBNNn.png)

---
![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650928447842/STU9DWG0O.png)

We were redirected to the homepage, and nothing happened :(. 

We will create a `getFeeds` function on the homepage to retrieve all the feeds.

`HomePage.js`

```js
//...

export default function Main() {

  //...

  /*
   * Get Feeds
   */
  const getFeeds = async () => {
    try {
      setLoading(true);
      const contract = await getContract();
      const AllFeeds = await contract.getAllFeeds();
      /*
       * We only need a title, category, coverImageHash, and author
       * pick those out
       */
      const formattedFeed = AllFeeds.map((feed) => {
        return {
          id: feed.id,
          title: feed.title,
          category: feed.category,
          coverImageHash: feed.coverImageHash,
          author: feed.author,
          date: new Date(feed.date * 1000),
        };
      });
      setFeeds(formattedFeed);
      setLoading(false);
    } catch (err) {
      error(`${err.message}`);
    }
  };

  /*
   * This runs our function when the page loads.
   */
  useEffect(() => {
    getFeeds();

    //...

  }, []);

  return (
    //...
  );
}

const Loader = () => {
  //...
};
```

Let's wait for the transaction to confirm. It takes a few seconds, and we should see it appear in real-time.

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650929024190/191XOK3MV.png)

 
### Building the Feed Page

Start by creating the `Feed.js` file inside the components folder and updating it with the following code snippet.

```js
import React from "react";
import { BiCheck } from "react-icons/bi";
import {
  AiFillTwitterCircle,
  AiFillLinkedin,
  AiFillRedditCircle,
} from "react-icons/ai";

export default function Feed({ feed }) {
  return (
    <div>
      <img
        className=" rounded-lg w-full bg-contain h-80"
        src={`https://ipfs.infura.io/ipfs/${feed.coverImageHash}`}
        alt="cover"
      />
      <div className="flex justify-between flex-row py-4 border-borderWhiteGray dark:border-borderGray border-b-2">
        <div>
          <h3 className="text-2xl dark:text-white">{feed.title}</h3>
          <p className="text-gray-500 mt-4">
            {feed.category} • {feed.date}
          </p>
        </div>
        <div className="flex flex-row items-center">
          <a
            className="bg-transparent dark:text-[#9CA3AF] py-2 px-6 border rounded-lg border-blue-600 mr-6 text-blue-600 hover:bg-blue-600 hover:text-white"
            href={`https://twitter.com/intent/tweet?text=${feed.title}&url=https://ipfs.infura.io/ipfs/${feed.coverImageHash}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <AiFillTwitterCircle />
          </a>
          <a
            className="bg-transparent dark:text-[#9CA3AF] py-2 px-6 border rounded-lg border-blue-600 mr-6 text-blue-500 hover:bg-blue-600 hover:text-white"
            href={`https://www.linkedin.com/shareArticle?mini=true&url=https://ipfs.infura.io/ipfs/${feed.coverImageHash}&title=${feed.title}&summary=${feed.description}&source=https://ipfs.infura.io/ipfs/${feed.coverImageHash}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <AiFillLinkedin />
          </a>
          <a
            className="bg-transparent dark:text-[#9CA3AF] py-2 px-6 border rounded-lg border-red-600 mr-6 text-red-600 hover:bg-red-600 hover:text-white"
            href={`https://www.reddit.com/submit?url=https://ipfs.infura.io/ipfs/${feed.coverImageHash}&title=${feed.title}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <AiFillRedditCircle />
          </a>
        </div>
      </div>

      <div className="flex mt-5 flex-row items-center ">
        <div className="flex items-center text-textSubTitle mt-1">
          Author: {feed?.author?.slice(0, 12)}...
          <BiCheck size="20px" color="green" className="ml-1" />
        </div>
      </div>
      <p className="text-sm text-black mt-4">{feed.description}</p>
    </div>
  );
}
```

Next, we will create the `FeedPage.js` file inside the `src` directory and update it with the code snippet below.

{% gist https://gist.github.com/Olanetsoft/897a11fa72a1b7e92d5cf98c1b79f1fd %}

In the snippet above, we retrieve a single feed and get related feeds in the feed category.

Next, we will update `App.js` with the following code snippet.


```js
//...

import Feed from "./FeedPage";

function App() {
  return (
    <Routes>
      //...
      <Route path="/feed" element={<Feed />} />
    </Routes>
  );
}

export default App;
```

Testing our Application 🥳

I have created several posts to test the application, as shown below.


![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650930645022/kIfzbagrv.png)

Single Feed Page

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650930620134/ZCz0Xd98n.png)

Social Share

![Build a Decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity](https://cdn.hashnode.com/res/hashnode/image/upload/v1650930788872/GbQBsOAxO.png)

> After upload, we might experience some delay or glitch when the feed appears on the homepage. We need to wait for some extra minutes, and then everything will become stable ;)


## Conclusion

This article teaches us to build a decentralized News Feed using Reactjs, TailwindCSS, Etherjs, IPFS & Solidity on Polygon Network.

## References

- [Polygon](polygon.technology)
- [IPFS](https://docs.ipfs.io/)
- [Cover Image](https://unsplash.com/s/photos/web-3.0)
- Design/Layout inspired by @[Suhail Kakar](@suhailkakar) 😊

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!!!