Algorand Blockchain Development using Reach - Part 7: React.js Front End and AlgoSigner Integration
In this tutorial, we will add a React User Interface to our Rock Paper Scissors game. By the end you will have a fully functioning dApp to show the world. As of this writing. This dApp has been compiled to. Binance / Matic / Ethereum Core and Algorand blockchains.
Requirements
Background
This tutorial is geared towards front end dApp developers. It will also be of interest to backend developers looking to integrate front ends skills to their bag of tricks.
Steps
Web Interaction
Reach Web applications rely on the Web browser to provide access to a consensus network account and its associated wallet. On Algorand
, the standard wallet is AlgoSigner
. If you want to test this code, you’ll need to install it and set it up. Furthermore, AlgoSigner
does not support multiple active accounts, so if you want to test Rock, Paper, Scissors! locally, you’ll need to have two separate browser instances: one to act as Alice and another to act as Bob.
—
To get started follow along with developer docs and YouTube videos for reference
We will focus on tut-8/index.js
, because tut-8/index.rsh
.
The code in this section does not use the scaffolding from the previous section. Reach comes with a convenience command for deleting scaffolded files:
$ ./reach unscaffold
Similarly, you do not need the previous index.mjs file, because we’ll be writing it completely from scratch to use React. You can run the following command to delete it:
$ rm index.mjs
Or, you can copy the index.rsh file into a new directory and work from there.
—
This code is supplemented with index.css and some views. These details are not specific to Reach, and are fairly trivial, so we will not explain the specifics of those files. If you run this locally, you’ll want to download those files. Your directory should look like:
.
├── index.css
├── index.js
├── index.rsh
└── views
├── AppViews.js
├── AttacherViews.js
├── DeployerViews.js
├── PlayerViews.js
└── render.js
—
Implementing the Front End with React.js
index.js
import React from 'react';
import AppViews from './views/AppViews';
import DeployerViews from './views/DeployerViews';
import AttacherViews from './views/AttacherViews';
import {renderDOM, renderView} from './views/render';
import './index.css';
import * as backend from './build/index.main.mjs';
import * as reach from '@reach-sh/stdlib/ALGO';
In the above code we import our view code and CSS. We also import the compiled backend
. We import the stdlib as reach.
index.js
const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
const {standardUnit} = reach;
const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
In the above code we define a few helpful constants and defaults for later, some corresponding to the enumerations we defined in Reach.
Defining the React Components
We start defining App as a React component, and tell it what to do once it mounts, which is the React term for starting.
index.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {view: 'ConnectAccount', ...defaults};
}
async componentDidMount() {
const acc = await reach.getDefaultAccount();
const balAtomic = await reach.balanceOf(acc);
const bal = reach.formatCurrency(balAtomic, 4);
this.setState({acc, bal});
try {
const faucet = await reach.getFaucet();
this.setState({view: 'FundAccount', faucet});
} catch (e) {
this.setState({view: 'DeployerOrAttacher'});
}
}
index.js
render() { return renderView(this, AppViews); }
}
In the above code we initialize the component state to display the ConnectAccount
view. We then hook into React’s componentDidMount lifecycle event, which is called when the component starts. we use getDefaultAccount
, which accesses the default browser account. For example, when used with Algorand, it can discover the currently-selected AlgoSigner account. We use getFaucet
to try and access the Reach developer testing network faucet. if getFaucet
was successful, we set the component state to display the FundAccount
if getFaucet
was unsuccessful, we set the component state to skip to the DeployerOrAttacher
view. We render the appropriate view
The on the next screen we will need to add our public key or mnemonic to continue with our dApp. Currently, Reach still needs delivered in TEAL 3 for a better user experience.
Then we will allow our frontend to connect to the Algorand blockchain.
async fundAccount(fundAmount) {
await reach.transfer(this.state.faucet, this.state.acc, reach.parseCurrency(fundAmount));
this.setState({view: 'DeployerOrAttacher'});
}
async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
In the above code, we define what happens when the user clicks the Fund Account button. We transfer funds from the faucet to the user’s account then we set the component state to display the DeployerOrAttacher
view. Lastly, we define what to do when the user clicks the Skip
button, which is to set the component state to display the DeployerOrAttacher
.
index.js
selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
In the above code we set a sub-component based on whether the user clicks Deployer
or Attacher
.
Next, we will define Player as a React component, which will be extended by the specialized components for Alice and Bob.
Our Web frontend needs to implement the participant interact interface for players, which we defined as:
index.rsh
const Player =
{ ...hasRandom,
getHand: Fun([], UInt),
seeOutcome: Fun([UInt], Null),
informTimeout: Fun([], Null) };
We will provide these callbacks via the React component directly.
class Player extends React.Component {
random() { return reach.hasRandom.random(); }
async getHand() { // Fun([], UInt)
const hand = await new Promise(resolveHandP => {
this.setState({view: 'GetHand', playable: true, resolveHandP});
});
this.setState({view: 'WaitingForResults', hand});
return handToInt[hand];
}
seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
informTimeout() { this.setState({view: 'Timeout'}); }
playHand(hand) { this.state.resolveHandP(hand); }
}
Implement the Participant Interfaces
In the above code, we provide the random
callback. We then we provide the getHand
callback. we set the component state to display the GetHand view, and wait for a Promise
which can be resolved via user interaction which occurs after the Promise is resolved, we set the component state to display the WaitingForResults
view. we provide the seeOutcome and informTimeout
callbacks, which set the component state to display the Done view and the Timeout view. We then define what happens when the user clicks Rock, Paper, or Scissors: The Promise is resolved.
Our Web frontend needs to implement the participant interact interface for Alice, which we defined as:
index.rsh
const Alice =
{ ...Player,
wager: UInt };
We will provide the wager value, and define some button handlers in order to trigger the deployment of the contract.
Implement the Deployer and Attacher Components
class Deployer extends Player {
constructor(props) {
super(props);
this.state = {view: 'SetWager'};
}
setWager(wager) { this.setState({view: 'Deploy', wager}); }
async deploy() {
const ctc = this.props.acc.deploy(backend);
this.setState({view: 'Deploying', ctc});
this.wager = reach.parseCurrency(this.state.wager); // UInt
backend.Alice(ctc, this);
const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
this.setState({view: 'WaitingForAttacher', ctcInfoStr});
}
render() { return renderView(this, DeployerViews); }
}
Finishing the React React dApp
We initialize the component state to display the Attach
view. We then define what happens when the user clicks the Attach
button. we call acc.attach
.Then we set the component state to display the Attaching view. We start running the Reach program as Bob, using the this
React component as the participant interact interface
object. we define the acceptWager callback.
We set the component state to display the AcceptTerms
view, and wait for a Promise which can be resolved via user interaction. We define what happens when the user clicks the Accept Terms and Pay Wager
button: the Promise is resolved, and we set the component state to display the WaitingForTurn view. We finally render the appropriate view
index.js
renderDOM(<App />);
Finally, we call a small helper function from render.js
to render our App component.
As a convenience for running the React development server, you can call:
REACH_CONNECTOR_MODE=ALGO ../algo_react/reach react
You can also use Reach in your own JavaScript project.
As usual, you can compile your Reach program index.rsh
to the backend build artifact build/index.main.mjs
with:
../algo_react/reach compile
Now our implementation of Rock, Paper, Scissors! is live in the browser! We can leverage callbacks in the participant interact interface to display to and gather information from the user, through any Web UI framework of our choice.
If we wanted to deploy this application to the world, then we would take the static files that React produces and host them on a Web server. These files embed your compiled Reach program, so there’s nothing more to do than provide them to the world.
This has been an exciting journey. I have enjoyed learning about Algorand and have gained some new friends along the way. I hope you enjoyed learning Reach with me. We are excited to see what you can IMAGINE