React Decentralized App to Sell eBooks (3/4)

This is part 3 of our four-tutorial series on creating a decentralized web app to sell ebooks. We will connect the front end with the smart contract in this part of the series. The interaction with the smart contract will be done using ether.js.

Move to the app.js file inside the source folder. We need to import the ethers library and the ABI of the smart contract.

This ABI or Application Binary Interface is nothing but the JSON format of the smart contract. This JSON format will allow us to talk to the smart contract from the front end. By using this ABI, we can call all the functions of the smart contract we have created.

Whenever you deploy the smart contract, a JSON format of the contract should have been created inside the artifacts folder. Just move to the artifacts folder, copy the file and paste it inside the source folder.

Now, React will be able to locate the directory of the smart contract. If the JSON file stays outside the source folder, it will not be accessible.

import Heading from "./components/Heading";
import abi from ".BookSell.json";
import { ethers } from "ethers"; 
import { useEffect, useState } from "react";

We will use the provider, signer, and contract variables regularly on the React dapp. That’s why it is better to store all these as an object inside a state variable. It will refrain us from creating those variables individually on the code, meanwhile saving our time.

We will use the useState() hook of React to implement this.

  const [state, setState] = useState({
    provider: null,
    signer: null,
    contract: null,
  });

We have created an object inside the useState() hook. This object takes the provider, signer, and the contract as keys. I am keeping the initial values for all the variables null.

To initialize all these variables, we will use the useEffect() hook of the React. Inside this useEffect() hook, first, we will create a connectMetamask() function to establish a connection with the Metamask.

But try to fetch the contract address and the contract ABI first. If you forget the contract address, move to the Etherscan website and insert the Goerli account address, you will get the contract address info in the latest transaction section.

const contractAddress = "0xede6825532dc2f6a8ac5b8dfc6874e80ae872dd4";
const contractABI = abi.abi;

Let’s create a try-catch method that will throw an error if the Metamask is not connected to the browser. Inside the try method, create a constant for the Ethereum object. You can consider the window as the Metamask. This Metamask injects an object called Ethereum into the browser that helps the browser interact with the blockchain.

const { ethereum } = window;

Now create an if else condition to check if the Metamask browser is detected. If the Metamask browser is injected, then it will ask for the account from Metamask. Otherwise, it will throw an error.

useEffect(() => {
    const connectMetamask = async () => {
      const contractAddress = "0xede6825532dc2f6a8ac5b8dfc6874e80ae872dd4";
      const contractABI = abi.abi;
      try {
        const { ethereum } = window;
        if (ethereum) {
          const account = await ethereum.request({
            method: "eth_requestAccounts",
          });

          const provider = new ethers.providers.Web3Provider(ethereum);
          const signer = provider.getSigner();
          const contract = new ethers.Contract(
            contractAddress,
            contractABI,
            signer
          );
          setState({ provider, signer, contract });
        } else {
          alert("Please install metamask");
        }
      } catch (error) {
        console.log(error);
      }
    };
    connectMetamask();
  }, []);

Inside the if condition, “eth_requestAccounts method” of the Ethereum library is used to request the account from the Metamask wallet.

We defined the provider with the help of the Web3Provider method of the ethers library. In our case, the provider would be Metamask.

We are committing a transaction, and for that, we must need a signer. We called the signer by using the getSigner() method of the provider.

Then we created an instance of our smart contract Contract() method of the library. This contract() method takes the Contract address, ABI and the signer as the parameters.

Now the provider, signer and contract are defined. So, we need to change the state of these three that we have defined as null earlier. With the help of setState(), we applied the changes.

If Metamask is not installed in the browser, then the else condition will show an alert, “Please install Metamask”, on the browser.

Our connectMetamask() function is ready. In the last line, we called the function to be activated.

Now let’s console.log the state to check if all the changes occur and the Metamask is connected to the browser.

console.log(state);

Now reload the React web page. If everything works well, you should get the provider, signer, and the contract on the console.

As you can see in my console, the state of the provider, signer, and contract was initially null. But now it returns the injected web3Provider as the provider, and our contract and signer state is also changed.

Create Components

Now we will create two functional components to write the codes for buying the book and fetching the receipts.

Create two components called Book.js and Receipt.js. Import react on those files and just type rsc. A component structure will be created automatically. If it doesn’t, then you can create the structure manually. Not a big deal.

We need to call the smart contract instance for the interaction. In the app.js file, we already created a state by which we can access the provider, signer, and contract instance. We will pass the state props from the app.js.

Inside the return function of the app.js, create a <div> and initiate the Book and Receipt components. Pass the state as props so we can access the state from the components file. Don’t forget to import the components in the app.js file.

return (
    <div className="bg-black min-h-full">
      <Heading></Heading>
      <Book state={state}></Book>
      <Receipt state={state}></Receipt>
    </div>
  );

Now move to the Book component. Pass the state props inside the fat arrow function. We need to create a form to collect the data of the buyers. The buyer will input the required data on that form and then submit it. I will use the flowbite website again to get some ready-made tailwind form components.

We need to customize the form that we are bringing from the website. We need to create three text input fields for the buyer’s name, the book name and the writer’s name. You can add some placeholder to make it looks nice. At the bottom, we need a submit button to submit the information. That is our simple form.

Create a formData() function that will be triggered when the form is submitted. We have created an onSubmit event handler on the form already.

Inside the formData() function, We need to destructor the contract instance from the state props so that we can call the different functions of the smart contract.

Now we want to collect the data from the form. To do that, we need to access the DOM elements.

💡 Info: DOM elements are the individual parts of a web page, such as text, images, and videos, that are represented as objects in a web browser’s Document Object Model (DOM). These objects can be manipulated by JavaScript to dynamically change the web page’s content, structure, and styling.

We can access DOM elements using the document.queryselector() method of JavaScript. But we should take advantage of React by using the useRef() hook. With the help of useRef() hook, we can access the particular data of the input field.

import { ethers } from "ethers";
import React, { useRef } from "react";

const Book = ({ state }) => {
  const form = useRef();
  const formData = async (event) => {
    event.preventDefault();
    const { contract } = state;
    const name = form.current.name.value;
    const book = form.current.book.value;
    const writer = form.current.writer.value;
    const amount = { value: ethers.utils.parseEther("0.001") };
    const transaction = await contract.buyBook(name, book, writer, amount);
    await transaction.wait();
    event.target.reset();
  };
  return (
    <div className="mt-10">
      <h1 className="text-center text-3xl font-extrabold font-serif text-red-400 pb-2">
        SUBSCRIBE HERE
      </h1>
      <form
        onSubmit={formData}
        ref={form}
        className="w-1/4 bg-slate-800 p-5 m-auto border rounded-xl"
      >
        <div className="mb-6">
          <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
            Your Name
          </label>
          <input
            type="text"
            name="name"
            className="bg-gray-500 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
            placeholder="Enter your name"
            required
          />
        </div>
        <div className="mb-6">
          <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
            Book Name
          </label>
          <input
            type="text"
            name="book"
            className="bg-gray-500 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
            placeholder="Enter The book's name"
            required
          />
        </div>
        <div className="mb-6">
          <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
            Writer Name
          </label>
          <input
            type="text"
            name="writer"
            className="bg-gray-500 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
            placeholder="Enter the writer's name"
            required
          />
        </div>

        <div className="flex items-center justify-center">
          <button
            type="submit"
            className="text-white bg-red-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-red-700 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
          >
            Subscribe
          </button>
        </div>
      </form>
    </div>
  );
};

I created a ref attribute for the form inside the return function, i.e., (ref = {form}). We also added a name attribute on each of the input fields. Using the name attribute, we can access each property’s input value with the help of useRef().

Form data is an async await function that triggers an event. Event.preventDefault() is used to prevent the unnecessary loading of the data.

We need to get the current properties of the input fields. That’s why we used “form.current” method to access the current input value of each input field.

We console.log the name, book, writer and contract just to check if our code is working.

Now we will access the buyBook function of the smart contract. If you can remember, we have already created a buyBook function for the transaction purpose. The buyBook() function will take the name, book, writer and amount as parameters. The first three are already defined inside the formData() function; we need to define the amount here.

We used the utils module of the ethers library to fix the transaction amount as 0.001 ETH.

We need to wait for some time for the transaction to take place. If the transaction is completed, “transaction is done” will be shown on the console.

Now move to the browser and refresh the page. Enter the buyer’s name, book and writer’s name in the input field. Then click on the buy button.

Metamask will pop up, asking for the confirmation of the transaction. Confirm the transaction and wait for some time. A transaction-confirmed notification will pop up after a while. In the console, you should get “transaction is done” written.

You can confirm the transaction by checking it on the Etherscan website. Just insert the Metamask account address of the Goerli wallet, and the transaction details will be displayed there.

Thanks for Reading — Keep it Up! 👇

That’s all for today. In the next part, we will try to bring the buyer’s info in to the user interface. Hope this will be interesting.

GitHub: https://github.com/finxter/bookSell