2.9 Web Interaction

In the last section, we made Rock, Paper, Scissors! run as a command-line application, without any changes to the Reach program. In this section, we again won’t be making any changes to the Reach program. Instead, we’ll replace the command-line interface with a Web interface.

We will use React.js for this tutorial, but the same principles apply to any Web framework.

If you’ve never used React before, here are some basics about how it works:
  • React programs are JavaScript programs that use a special library that allows you to mix HTML inside of the body of your JavaScript.

  • React has a special compiler that combines a bundle of JavaScript programs, and all their dependencies, into one large file that can be deployed on a static Web server. This is called "packing".

  • When you’re developing and testing with React, you run a special development Web server that watches updates this packed file every time you modify your source files, so you don’t have to constantly run the compiler.

  • Reach automates the process of starting this development server for you when you run ./reach react and gives you access to it at http://localhost:3000/.

Similarly, in this tutorial, we assume that we will be deploying (and testing) with Ethereum. Reach Web applications rely on the Web browser to provide access to a consensus network account and its associated wallet. On Ethereum, the standard wallet is MetaMask. If you want to test this code, you’ll need to install it and set it up. Furthermore, MetaMask 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.

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

We will focus on tut-8/index.js, because tut-8/index.rsh is the same as previous sections.

 1    import React from 'react';
 2    import AppViews from './views/AppViews';
 3    import DeployerViews from './views/DeployerViews';
 4    import AttacherViews from './views/AttacherViews';
 5    import {renderDOM, renderView} from './views/render';
 6    import './index.css';
 7    import * as backend from './build/index.main.mjs';
 8    import * as reach from '@reach-sh/stdlib/ETH';
 9    
..    // ...

On lines 1 thru 6, we import our view code and CSS. On line 7, we import the compiled backend. On line 8, we import the stdlib as reach.

To run on Algorand, change the import on line 8.

import * as reach from '@reach-sh/stdlib/ALGO'

..    // ...
10    const handToInt = {'ROCK': 0, 'PAPER': 1, 'SCISSORS': 2};
11    const intToOutcome = ['Bob wins!', 'Draw!', 'Alice wins!'];
12    const {standardUnit} = reach;
13    const defaults = {defaultFundAmt: '10', defaultWager: '3', standardUnit};
14    
..    // ...

On these lines we define a few helpful constants and defaults for later, some corresponding to the enumerations we defined in Reach.

We start defining App as a React component, and tell it what to do once it mounts, which is the React term for starting.

Figure 1: The ConnectAccount view. See: AppViews.ConnectAccount

..    // ...
15    class App extends React.Component {
16      constructor(props) {
17        super(props);
18        this.state = {view: 'ConnectAccount', ...defaults};
19      }
20      async componentDidMount() {
21        const acc = await reach.getDefaultAccount();
22        const balAtomic = await reach.balanceOf(acc);
23        const bal = reach.formatCurrency(balAtomic, 4);
24        this.setState({acc, bal});
25        try {
26          const faucet = await reach.getFaucet();
27          this.setState({view: 'FundAccount', faucet});
28        } catch (e) {
29          this.setState({view: 'DeployerOrAttacher'});
30        }
31      }
..    // ...

..    // ...
39      render() { return renderView(this, AppViews); }
40    }
41    
..    // ...

Figure 2: The FundAccount view. See: AppViews.FundAccount

Next, we define callbacks on App for what to do when the user clicks certain buttons.

..    . // ...
32     async fundAccount(fundAmount) {
33       await reach.transfer(this.state.faucet, this.state.acc, reach.parseCurrency(fundAmount));
34       this.setState({view: 'DeployerOrAttacher'});
35     }
36     async skipFundAccount() { this.setState({view: 'DeployerOrAttacher'}); }
..    . // ...

Figure 3: The DeployerOrAttacher view. See: AppViews.DeployerOrAttacher

..    // ...
37      selectAttacher() { this.setState({view: 'Wrapper', ContentView: Attacher}); }
38      selectDeployer() { this.setState({view: 'Wrapper', ContentView: Deployer}); }
..    // ...

On lines 37 and 38, 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.

Figure 4: The GetHand view. See: PlayerViews.GetHand

Our Web frontend needs to implement the participant interact interface for players, which we defined as:

..    // ...
20    const Player =
21          { ...hasRandom,
22            getHand: Fun([], UInt),
23            seeOutcome: Fun([UInt], Null),
24            informTimeout: Fun([], Null) };
..    // ...

We will provide these callbacks via the React component directly.

..    // ...
42    class Player extends React.Component {
43      random() { return reach.hasRandom.random(); }
44      async getHand() { // Fun([], UInt)
45        const hand = await new Promise(resolveHandP => {
46          this.setState({view: 'GetHand', playable: true, resolveHandP});
47        });
48        this.setState({view: 'WaitingForResults', hand});
49        return handToInt[hand];
50      }
51      seeOutcome(i) { this.setState({view: 'Done', outcome: intToOutcome[i]}); }
52      informTimeout() { this.setState({view: 'Timeout'}); }
53      playHand(hand) { this.state.resolveHandP(hand); }
54    }
55    
..    // ...

Figure 5: The WaitingForResults view. See: PlayerViews.WaitingForResults

Figure 6: The Done view. See: PlayerViews.Done

Figure 7: The Timeout view. See: PlayerViews.Timeout

Next, we will define Deployer as a React component for Alice, which extends Player.

Figure 8: The SetWager view. See: DeployerViews.SetWager

Figure 9: The Deploy view. See: DeployerViews.Deploy

Our Web frontend needs to implement the participant interact interface for Alice, which we defined as:

..    // ...
25    const Alice =
26          { ...Player,
27            wager: UInt };
..    // ...

We will provide the wager value, and define some button handlers in order to trigger the deployment of the contract.

..    // ...
56    class Deployer extends Player {
57      constructor(props) {
58        super(props);
59        this.state = {view: 'SetWager'};
60      }
61      setWager(wager) { this.setState({view: 'Deploy', wager}); }
62      async deploy() {
63        const ctc = this.props.acc.deploy(backend);
64        this.setState({view: 'Deploying', ctc});
65        this.wager = reach.parseCurrency(this.state.wager); // UInt
66        backend.Alice(ctc, this);
67        const ctcInfoStr = JSON.stringify(await ctc.getInfo(), null, 2);
68        this.setState({view: 'WaitingForAttacher', ctcInfoStr});
69      }
70      render() { return renderView(this, DeployerViews); }
71    }
72    
..    // ...

Figure 10: The Deploying view. See: DeployerViews.Deploying

Figure 11: The WaitingForAttacher view. See: DeployerViews.WaitingForAttacher

Figure 12: The Attach view. See: AttacherViews.Attach

Figure 13: The Attaching view. See: AttacherViews.Attaching

Our Web frontend needs to implement the participant interact interface for Bob, which we defined as:

..    // ...
28    const Bob =
29          { ...Player,
30            acceptWager: Fun([UInt], Null) };
..    // ...

We will provide the acceptWager callback, and define some button handlers in order to attach to the deployed contract.

..    // ...
73    class Attacher extends Player {
74      constructor(props) {
75        super(props);
76        this.state = {view: 'Attach'};
77      }
78      attach(ctcInfoStr) {
79        const ctc = this.props.acc.attach(backend, JSON.parse(ctcInfoStr));
80        this.setState({view: 'Attaching'});
81        backend.Bob(ctc, this);
82      }
83      async acceptWager(wagerAtomic) { // Fun([UInt], Null)
84        const wager = reach.formatCurrency(wagerAtomic, 4);
85        return await new Promise(resolveAcceptedP => {
86          this.setState({view: 'AcceptTerms', wager, resolveAcceptedP});
87        });
88      }
89      termsAccepted() {
90        this.state.resolveAcceptedP();
91        this.setState({view: 'WaitingForTurn'});
92      }
93      render() { return renderView(this, AttacherViews); }
94    }
95    
..    // ...

Figure 14: The AcceptTerms view. See: AttacherViews.AcceptTerms

Figure 15: The WaitingForTurn view. See: AttacherViews.WaitingForTurn

..    // ...
96    renderDOM(<App />);

Finally, we call a small helper function from tut-8/views/render.js to render our App component.

As a convenience for running the React development server, you can call:

  $ ./reach react

To run the React development server with Algorand, you can call:

  $ REACH_CONNECTOR_MODE=ALGO ./reach react

If you’d like to instead use Reach in your own JavaScript project, you can call:

  $ npm install @reach-sh/stdlib

The Reach standard library is undergoing continual improvement and is updated often. If you are experiencing issues with the Node.js package, try updating!

As usual, you can compile your Reach program index.rsh to the backend build artifact build/index.main.mjs with:

  $ ./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.

In the next section, we’ll summarize where we’ve gone and direct you to the next step of your journey to decentralized application mastery.

Check your understanding: True or false: Reach integrates with all Web interface libraries, like React, Vue, and so on, because Reach frontends are just normal JavaScript programs.

Answer:

True

Check your understanding: True or false: Reach accelerates your development with React by baking-in a React development server and the deployment process to test React programs locally.

Answer:

True