Create a Dapp
Building a React App for Solidity Contracts with Vite, Tailwind, and Web3
This guide will show you how to create a React app to interact with Ethereum smart contracts using Web3.js. We will set up a Vite project with Tailwind CSS styling and build interfaces for two example contracts: a Vending Machine and a Lottery.
Steps to Set Up the React App
Step 1: Initialize a Vite Project
Run the following commands to create a Vite React app, install dependencies, and start the development server:
npm init vite@latest
cd <project name>
npm install
npm run devStep 2: Install and Configure Tailwind CSS
To add Tailwind CSS, install the necessary packages:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss initIn the tailwind.config.js file, configure Tailwind to remove unused styles in production by specifying the files to scan:
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}Add the Tailwind directives to your CSS file (e.g., src/index.css):
@tailwind base;
@tailwind components;
@tailwind utilities;Step 3: Install Web3.js
Install Web3.js to connect your React app to the Ethereum blockchain:
npm install web3Vending Machine Contract
The Vending Machine smart contract allows users to purchase items with Ether and allows the owner to restock inventory.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract VendingMachine {
    address public owner;
    mapping (address => uint) public cokeBalances;
    constructor() {
        owner = msg.sender;
        cokeBalances[address(this)] = 100;
    }
    function getVendingMachineBalance() public view returns (uint) {
        return cokeBalances[address(this)];
    }
    function restock(uint amount) public {
        require(msg.sender == owner, "Only the owner can restock.");
        cokeBalances[address(this)] += amount;
    }
    function purchase(uint amount) public payable {
        require(msg.value >= amount * 2 ether, "You must pay at least 2 ETH per item");
        require(cokeBalances[address(this)] >= amount, "Not enough items in stock to complete this purchase");
        cokeBalances[address(this)] -= amount;
        cokeBalances[msg.sender] += amount;
    }
}Lottery Contract
The Lottery smart contract allows players to join by paying an entry fee, with the contract owner able to pick a winner at random.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract Lottery {
    address public owner;
    address payable[] public players;
    uint public lotteryId;
    mapping (uint => address payable) public lotteryHistory;
    constructor() {
        owner = msg.sender;
        lotteryId = 1;
    }
    function getWinnerByLottery(uint lottery) public view returns (address payable) {
        return lotteryHistory[lottery];
    }
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
    function getPlayers() public view returns (address payable[] memory) {
        return players;
    }
    function enter() public payable {
        require(msg.value > .001 ether);
        players.push(payable(msg.sender));
    }
    function getRandomNumber() public view returns (uint) {
        return uint(keccak256(abi.encodePacked(owner, block.timestamp)));
    }
    function pickWinner() public onlyowner {
        uint index = getRandomNumber() % players.length;
        players[index].transfer(address(this).balance);
        lotteryHistory[lotteryId] = players[index];
        lotteryId++;
        players = new address payable ;
    }
    modifier onlyowner() {
      require(msg.sender == owner);
      _;
    }
}Integrating the Contracts with the React App
Set up Web3 Provider and ABI files for each contract in
src/contracts/.Create components to interact with the contracts.
Example React Component for Vending Machine
import React, { useEffect, useState } from "react";
import Web3 from "web3";
import VendingMachineABI from "./contracts/VendingMachine.json";
const VendingMachine = () => {
    const [web3, setWeb3] = useState(null);
    const [contract, setContract] = useState(null);
    const [balance, setBalance] = useState(0);
    const [amount, setAmount] = useState(1);
    const [userAddress, setUserAddress] = useState("");
    useEffect(() => {
        async function loadWeb3() {
            const web3Instance = new Web3(window.ethereum);
            setWeb3(web3Instance);
            const vendingMachineContract = new web3Instance.eth.Contract(
                VendingMachineABI.abi,
                "<VENDING_MACHINE_CONTRACT_ADDRESS>"
            );
            setContract(vendingMachineContract);
            const accounts = await web3Instance.eth.requestAccounts();
            setUserAddress(accounts[0]);
            const balance = await vendingMachineContract.methods.getVendingMachineBalance().call();
            setBalance(balance);
        }
        loadWeb3();
    }, []);
    const handlePurchase = async () => {
        await contract.methods.purchase(amount).send({ from: userAddress, value: web3.utils.toWei((2 * amount).toString(), "ether") });
        const newBalance = await contract.methods.getVendingMachineBalance().call();
        setBalance(newBalance);
    };
    return (
        <div className="container mx-auto p-4">
            <h1 className="text-2xl font-bold">Vending Machine</h1>
            <p>Available Balance: {balance}</p>
            <input
                type="number"
                value={amount}
                onChange={(e) => setAmount(e.target.value)}
                className="border rounded p-2"
            />
            <button onClick={handlePurchase} className="bg-blue-500 text-white p-2 rounded">Buy Item</button>
        </div>
    );
};
export default VendingMachine;Testing the Contracts with Hardhat
Step 1: Install Hardhat
If you haven’t already:
npm install --save-dev hardhatStep 2: Create Test Files
Create test files for each contract. For instance, a VendingMachine test might look like this:
const { expect } = require("chai");
describe("VendingMachine", function () {
    let VendingMachine, vendingMachine, owner, user;
    beforeEach(async function () {
        [owner, user] = await ethers.getSigners();
        VendingMachine = await ethers.getContractFactory("VendingMachine");
        vendingMachine = await VendingMachine.deploy();
        await vendingMachine.deployed();
    });
    it("should initialize with a balance of 100", async function () {
        expect(await vendingMachine.getVendingMachineBalance()).to.equal(100);
    });
    it("should allow owner to restock", async function () {
        await vendingMachine.connect(owner).restock(50);
        expect(await vendingMachine.getVendingMachineBalance()).to.equal(150);
    });
    it("should allow user to purchase items", async function () {
        await vendingMachine.connect(user).purchase(1, { value: ethers.utils.parseEther("2") });
        expect(await vendingMachine.cokeBalances(user.address)).to.equal(1);
    });
});Run the tests with:
npx hardhat testLast updated