By the end of this lesson, you will be able to:
In the previous lesson, we demonstrated how to construct a boilerplate structure for interaction with the HelloWorld smart contract that we deployed on the BNB Smart Chain Testnet by following the previous lesson on deploying smart contracts. In this lesson, we illustrate how to connect our UI to Web3js library for interaction with the smart contract.
This demo illustrates integration of Web3js library with a simple user interface based on Reactjs for interaction with smart contract via the UI. This is a hands-on guide and readers are encourged to perform these tasks along for a better understanding.
Before starting off with this demo, it is necessary to make sure you have completed the previous lesson based on constructing a user interface.
Install the Web3js Library
Run the following command to install the Web3js
library. Make sure you are in the root folder of your application's project, in our case the frontend
folder.
npm install web3 --save
After installing Web3js library, the next step is to import it into your App.js
file and use it to access the smart contract. Add the following line in the App.js
to import the Web3 library.
import Web3 from 'web3';
Create three global variables in the App.js
file selectedAccount
, helloWorldContract
, and isInitialized
which will store the current account of Metamask, the HelloWorld Contract ABI and a boolean value to indicate whether the dapp as been initialized which the wallet and smart contract details. Add the following lines after the import statements and outside the App
component.
let selectedAccount = null;
let helloWorldContract = null;
let isInitialized = false;
Create an init()
function which will be performing the following tasks:
let provider = window.ethereum;
..request
to the provider
to gain access to the user's account.isInitialized
variable is set to true
.const init = async () => {
let provider = window.ethereum;
if (typeof provider !== 'undefined') {
provider.request({method: 'eth_requestAccounts' }).then((accounts) => {
selectedAccount = accounts[0];
console.log(`Selected account is ${selectedAccount}`);
})
.catch((err) => {
console.log(err);
});
window.ethereum.on('accountsChanged', function (accounts){
selectedAccount = accounts[0];
console.log(`Selected account changed to ${selectedAccount}`);
});
}
const web3 = new Web3(provider);
const networkId = await web3.eth.net.getId();
helloWorldContract = new web3.eth.Contract(HelloWorldContract.abi,HelloWorldContract.networks[networkId].address);
isInitialized = true;
};
Also add the call to init()
in the useEffect
function.
useEffect(() => {
checkWalletIsConnected();
init();
}, [])
Update the appContent with the following to include onClick
functions for buttons and ref
for the input field.
const appContent = () => {
return (
<div>
<div>
<label htmlFor="name" className="col-lg-2 control-label">
Enter a Name
</label>
<br/>
<input ref={inputRef} id="name" type="text" placeholder="Enter Name to Greet"/>
<br/>
<button id="buttonSave" onClick={()=>setGreetName()}>Save Name</button>
<br/>
<button id="buttonMessage" onClick={()=>printMessage()}>Greetings</button>
</div>
</div>
)
}
To get the value of an uncontrolled input field on button click in React the general steps are:
ref
for the input field.onClick
event handler on the button.ref
object to access the current input value in the event handler.Initialize a state variable that utilizes useRef for maintaining state of the input field on our UI. Define this in the App
component outside of other defined functions.
const inputRef = useRef(null);
We have already added a ref
as inputRef
to the input field. Let's define the onClick
handler setGreetName
to fetch the current value in the input field and send it to our smart contract. Define the setGreetName
in the App
component.
const setGreetName = async () => {
if(inputRef.current.value === ""){
alert("Name cannot be empty!");
}else{
if (!isInitialized) {
await init();
}
await helloWorldContract.methods.setName(inputRef.current.value).send({from: selectedAccount})
.then( async ()=>{
alert(await helloWorldContract.methods.getMessage().call());
})
.catch((err) => {
alert(err.message);
});
}
}
Define a printMessage
function in the App
component which is the onClick
handler for Greeting button, and is reponsible for making a call to the getMessage()
function of the smart contract to fetch the greeting message to print.
const printMessage = async () => {
if (!isInitialized) {
await init();
}
alert(await helloWorldContract.methods.getMessage().call());
}
At the end your App.js file should look like as follows
import { useEffect, useState, useRef } from 'react';
import './App.css';
import HelloWorldContract from './HelloWorld.json';
import Web3 from 'web3';
let selectedAccount = null;
let helloWorldContract = null;
let isInitialized = false;
function App() {
const [currentAccount, setCurrentAccount] = useState(null);
const inputRef = useRef(null);
const init = async () => {
let provider = window.ethereum;
if (typeof provider !== 'undefined') {
provider.request({method: 'eth_requestAccounts' }).then((accounts) => {
selectedAccount = accounts[0];
console.log(`Selected account is ${selectedAccount}`);
})
.catch((err) => {
console.log(err);
});
window.ethereum.on('accountsChanged', function (accounts){
selectedAccount = accounts[0];
console.log(`Selected account changed to ${selectedAccount}`);
});
}
const web3 = new Web3(provider);
const networkId = await web3.eth.net.getId();
helloWorldContract = new web3.eth.Contract(HelloWorldContract.abi,HelloWorldContract.networks[networkId].address);
isInitialized = true;
};
const printMessage = async () => {
if (!isInitialized) {
await init();
}
alert(await helloWorldContract.methods.getMessage().call());
}
const setGreetName = async () => {
if(inputRef.current.value === ""){
alert("Name cannot be empty!");
}else{
if (!isInitialized) {
await init();
}
await helloWorldContract.methods.setName(inputRef.current.value).send({from: selectedAccount})
.then( async ()=>{
alert(await helloWorldContract.methods.getMessage().call());
})
.catch((err) => {
alert(err.message);
});
}
}
const connectWalletButton = () => {
return (
<button onClick={connectWalletHandler} className='cta-button connect-wallet-button'>
Connect Wallet
</button>
)
}
const appContent = () => {
return (
<div>
<div>
<label htmlFor="name" className="col-lg-2 control-label">
Enter a Name
</label>
<br/>
<input ref={inputRef} id="name" type="text" placeholder="Enter Name to Greet"/>
<br/>
<button id="buttonSave" onClick={()=>setGreetName()}>Save Name</button>
<br/>
<button id="buttonMessage" onClick={()=>printMessage()}>Greetings</button>
</div>
</div>
)
}
const checkWalletIsConnected = async() => {
const { ethereum } = window;
if(!ethereum){
console.log("Make sure you have Metamask installed!");
return;
} else{
console.log("Metamask is Installed. We're ready to go !")
}
const accounts = await ethereum.request({method: 'eth_accounts'});
if(accounts.length !== 0) {
const account = accounts[0];
console.log("Found an authorized account: ", account);
setCurrentAccount(account);
}else {
console.log("No authorized account found!");
alert("No authorized account found!");
}
}
const connectWalletHandler = async () => {
const {ethereum} = window;
if(!ethereum){
alert("Please install Metamask!!");
return;
}
try{
const accounts = await ethereum.request({method: 'eth_requestAccounts'});
console.log("Found an account ! Address: ", accounts[0]);
setCurrentAccount(accounts[0]);
} catch(err){
alert(err.message);
console.log(err);
}
}
useEffect(() => {
checkWalletIsConnected();
init();
}, [])
return (
<div className="App">
<header className="App-header">
<h1>Hello World Dapp</h1>
</header>
{currentAccount ? appContent() : connectWalletButton()}
</div>
);
}
export default App;
🎉 Congratulations! 🎉 You have successfully connected a simple Reactjs based frontend with the your smart contract deployed on the BNB Smart Chain.
To check the functionality of your dapp, restart the application using npm start
In this last lesson of Module 3, we covered how to use Web3js library to connect the frontend of your dapp to a web3 wallet like Metamask and also to initialize dapp with the smart contract details.