【初めてのDApps開発②】ReactでシンプルなDAppsを作ってみよう！~フロントエンド編~

#はじめに
[※注意!!]この記事は[【初めてのDApps開発①】](https://qiita.com/sho11hei12-1998/items/75ad4abf1b4da6749fd6)の続編です。まだ読んでいない人はそちらを読んでからこの記事を読んで下さい。


今回は最後の項目のReactを用いたフロントエンド開発を行います。
>1. Remixを用いたスマートコントラクト開発
2. Truffleを用いたスマートコントラクトのデプロイ
3. **Reactを用いたフロントエンド開発**

「Reactというフロントエンドフレームワークを使ってweb3.jsを経由してmetamaskを動かし, スマートコントラクトを動作させる」ということをやっていきます。
つまりこのようなイメージになります。






#作っていくアプリケーション
本記事では、[【初めてのDApps開発①】](https://qiita.com/sho11hei12-1998/items/75ad4abf1b4da6749fd6)でデプロイしたスマートコントラクトを使いながらアプリケーションの見た目の部分を作っていきます。

完成するアプリケーションは、以下のようになります。
URL: [https://diarydapp-2a69c.web.app](https://diarydapp-2a69c.web.app)

![スクリーンショット 2020-11-29 11.29.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/46ac4afc-33b7-05cc-4c3a-29111a9a12b8.png)


では、一緒に作っていきましょう！！！

#セットアップ
まずディレクトリを作ります。

そこにweb3がすぐに使えるようになっているReact Truffle Boxというレポジトリをクローンします. `npm install` を実行することで `package.json` 書かれている `dependencies` をインストールしてくれます。

```
$ git clone https://github.com/truffle-box/react-box.git
$ cd react-box/client
$ npm install
```

私は[【初めてのDApps開発①】]()で作成した`dapps-deploy`ファイルと一まとめにして管理することにしました！
![スクリーンショット 2020-12-02 19.49.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/2609f0a3-15cf-acba-bf6f-1321e8de36e8.png)


さて、[【初めてのDApps開発①】](https://qiita.com/sho11hei12-1998/items/75ad4abf1b4da6749fd6)で作成した`dapps-deploy` ディレクトリ下の `build/contract` に `Resister.json` ファイルが生成されています。これはコントラクトのコンパイル時に生成されたものです。中身を丸ごとコピーしましょう。

そして `react-box`ディレクトリの`client/src` 下に `Resister.json` というファイルを作ってそこに貼ります。


#Reactを記述していく！
まず、`client/src` 下の `App.js` をこのように書き換えましょう。

```App.js
import React from "react";
import Resister from "./Resister.json";
import getWeb3 from "./getWeb3";
import "./App.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      web3: null,
      accounts: null,
      contract: null,
      name: null,
      age: null,
      hobby: null,
      address: "",
      outputName: null,
      outputAge: null,
      outputHobby: null,
    };
  }

  componentDidMount = async () => {
    try {
      const web3 = await getWeb3();

      const accounts = await web3.eth.getAccounts();
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = Resister.networks[networkId];
      const instance = new web3.eth.Contract(
        Resister.abi,
        deployedNetwork && deployedNetwork.address
      );

      this.setState({ web3, accounts, contract: instance });
    } catch (error) {
      alert(
        `Failed to load web3, accounts, or contract. Check console for details.`
      );
      console.error(error);
    }

    const { accounts } = this.state;
    console.log(accounts);
  };

  render() {
    return (
      <div className="App">
      </div>
    );
  }
}

export default App;
```
`componentDidMount`関数 は react のライフサイクルの一種でレンダリングがされた直後に実行される関数です。web3とコントラクトを使えるようにする処理を書いています。
`render`はHTML的なものを書いていく所です。
とりあえず起動してみましょう。

```
$ npm start
```

まだ何も表示されないはずです。

###関数の作成
関数の作成をしていきます。

```.js
// アカウント情報の登録をする関数
writeRecord = async () => {
  const { accounts, contract, name, age, hobby } = this.state;
  const result = await contract.methods.registerAccount(name, age, hobby).send({
    from: accounts[0],
  });
  console.log(result);

  if (result.status === true) {
    alert('会員登録が完了しました。');
  }
};
```

コントラクトで定義した `registerAccount 関数`を実行する関数です。ユーザーの `name、age、hobby` という入力値を受け取って、引数に入れています。`registerAccount 関数` はコントラクトのStorageにデータを書き込む関数なのでGas代を支払うアカウントを明記しています。ここでは自分のデータを書き込むユーザーが支払うように設定されています。


```.js
// アカウント情報を読み込む関数
viewRecord = async () => {
  const { contract, accounts } = this.state;
  console.log(contract);

  const result = await contract.methods.viewAccount(accounts[0]).call();
  console.log(result);

  const outputName = result[0];
  const outputAge = result[1];
  const outputHobby = result[2];
  this.setState({ outputName, outputAge, outputHobby });
};
```

コントラクトで定義した `viewAccount 関数`を実行する関数です。
`registerAccount 関数`と違うところはGas代を支払うユーザーを明記していない点です。`viewAccount 関数`はStorageにデータを書き込まない(見るだけ)なのでGas代を支払う必要がありません。また受け取った結果を表示するためにstateに入れています。

```.js
handleChange = (name) => (event) => {
  this.setState({ [name]: event.target.value });
};
```

ユーザーの入力値を受け取ってstateに入れる関数です。
以上で関数の作成は完了です。UIの作成に移ります。

###UIの作成
ReactだけでシンプルなUIを作っていきます！

```.js
render() {
    return (
      <div className="App">
        <br />
        <form>
          <div>
            <label>氏名：</label>
            <input
              onChange={this.handleChange("name")} />
          </div>

          <div>
            <label>年齢：</label>
            <input
              onChange={this.handleChange("age")} />
          </div>

          <div>
            <label>趣味：</label>
            <input
              onChange={this.handleChange("hobby")} />
          </div>

          <button type='button' onClick={this.writeRecord}>
            会員登録
          </button>
        </form>

        <br />
        <br />

        <form>
          <label>検索したいアドレスを入力してください。</label>
          <input onChange={this.handleChange("address")} />

          <button type='button' onClick={this.viewRecord}>
            検索
            </button>
        </form>

        <br />
        <br />

        {this.state.outputName ? <p>氏名: {this.state.outputName}</p> : <p></p>}
        {this.state.outputAge ? <p>年齢: {this.state.outputAge}</p> : <p></p>}
        {this.state.outputHobby ? <p>趣味: {this.state.outputHobby}</p> : <p></p>}

      </div>

    );
  }
```

以上の関数、UIの記述を行った後のApp.jsがこちらです。↓

```App.js
import React from "react";
import Resister from "./Resister.json";
import getWeb3 from "./getWeb3";
import "./App.css";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      web3: null,
      accounts: null,
      contract: null,
      name: null,
      age: null,
      hobby: null,
      address: "",
      outputName: null,
      outputAge: null,
      outputHobby: null,
    };
  }

  componentDidMount = async () => {
    try {
      const web3 = await getWeb3();

      const accounts = await web3.eth.getAccounts();
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = Resister.networks[networkId];
      const instance = new web3.eth.Contract(
        Resister.abi,
        deployedNetwork && deployedNetwork.address
      );

      this.setState({ web3, accounts, contract: instance });
    } catch (error) {
      alert(
        `Failed to load web3, accounts, or contract. Check console for details.`
      );
      console.error(error);
    }

    const { accounts } = this.state;
    console.log(accounts);
  };

  // アカウント情報の登録
  writeRecord = async () => {
    const { accounts, contract, name, age, hobby } = this.state;
    const result = await contract.methods.registerAccount(name, age, hobby).send({
      from: accounts[0],
    });
    console.log(result);

    if (result.status === true) {
      alert('会員登録が完了しました。');
    }
  };

  // アカウント情報の読み込み
  viewRecord = async () => {
    const { contract, accounts } = this.state;
    console.log(contract);

    const result = await contract.methods.viewAccount(accounts[0]).call();
    console.log(result);

    const outputName = result[0];
    const outputAge = result[1];
    const outputHobby = result[2];
    this.setState({ outputName, outputAge, outputHobby });
  };

  handleChange = (name) => (event) => {
    this.setState({ [name]: event.target.value });
  };

  render() {
    return (
      <div className="App">
        <br />
        <form>
          <div>
            <label>氏名：</label>
            <input
              onChange={this.handleChange("name")} />
          </div>

          <div>
            <label>年齢：</label>
            <input
              onChange={this.handleChange("age")} />
          </div>

          <div>
            <label>趣味：</label>
            <input
              onChange={this.handleChange("hobby")} />
          </div>

          <button type='button' onClick={this.writeRecord}>
            会員登録
          </button>
        </form>

        <br />
        <br />

        <form>
          <label>検索したいアドレスを入力してください。</label>
          <input onChange={this.handleChange("address")} />

          <button type='button' onClick={this.viewRecord}>
            検索
            </button>
        </form>

        <br />
        <br />

        {this.state.outputName ? <p>氏名: {this.state.outputName}</p> : <p></p>}
        {this.state.outputAge ? <p>年齢: {this.state.outputAge}</p> : <p></p>}
        {this.state.outputHobby ? <p>趣味: {this.state.outputHobby}</p> : <p></p>}

      </div>

    );
  }
}

export default App;
```

ここで確認のために起動を行います。`npm start`を打ち込みます。
すると以下のように表示されるはずです。

![スクリーンショット 2020-11-29 9.59.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/e4c9ac41-941e-ae9b-c907-eea0b863f7e6.png)


シンプルなUIが完成しました。👏

ここまでのreact-box/client/src内のファイル構成はこのようになっています。
![スクリーンショット 2020-12-02 19.43.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/4e3e3df7-ba70-6b02-13ad-407d15622ad1.png)


#テスト
では、実際に動かしてみましょう。
その前に！MetamaskとReact Appとの接続を許可する必要があります。

1. Metamaskを起動。
2. 画像上部のネットワーク名を`Rinkebyテストネットワーク`に設定。

3. 水色で囲まれた右上のボタンをクリック→、`Connected sites`をクリック→、`Manually connect to current site`→、次へ→、`Connect`。
4. 赤枠で囲った箇所が`Connected`に変わっていれば、React Appとの接続が完了しています。
![スクリーンショット 2020-11-29 10.21.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/afa48446-a584-aec4-fa27-22e695ca6968.png)


では、いよいよテストしていきます。
適当に氏名、年齢、趣味を入力して会員登録ボタンを押してみてください。

![スクリーンショット 2020-11-29 10.39.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/3d6ec5e3-6457-9f27-6e1c-5716105b6007.png)


自動的にmetamaskが起動するので**確認ボタン**を押してください。
すると会員情報がブロックチェーン上に記録されます。ディベロッパーツールでは、ブロックに関する情報を見ることができます。

![スクリーンショット 2020-11-29 10.06.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/b5816052-f314-c2f2-3d95-c01b567c7583.png)

![スクリーンショット 2020-11-29 10.14.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/eb2cf43d-acb2-df4f-eea6-0016bd7aab55.png)


次に登録した情報を閲覧をしてみましょう。metamaskを開いてアドレスをコピーします。

![スクリーンショット 2020-11-29 10.41.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/7c1fe149-7749-b198-feba-706d0c28b81d.png)


コピーしたアドレスを入力して検索ボタンを押してみてください。

![スクリーンショット 2020-11-28 15.33.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/0c6b0b3e-8f45-f1bf-2674-a92d79db959f.png)

会員情報の閲覧ができました！！！👏👏

次は、このアプリケーションのデザインをカッコよくしていきたいと思います。

#React-Bootstrapを用いてアプリケーションの見た目をカッコよくしていく
ここから先は、冒頭で紹介したようなアプリケーションの見た目にしていく作業になります。
（※アプリケーションの見た目にこだわらない方であればこのセクションはとばしても全然問題ないです）

では、React-Bootstrapを用いてApp.jsを編集していきます。
react-bootstrapのインストール方法と使い方は以下の記事を参考にして下さい。
[React Bootstrap 公式ドキュメント](https://react-bootstrap.github.io/getting-started/introduction)


編集後のApp.jsがこちらになります。

```App.js
import React from "react";
import Resister from "./Resister.json";
import getWeb3 from "./getWeb3";

import { Row, Col, Button, Form, Modal } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";

import "./App.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      web3: null,
      accounts: null,
      contract: null,
      name: null,
      age: null,
      hobby: null,
      address: "",
      outputName: null,
      outputAge: null,
      outputHobby: null,

      ////
      lines: [],

      // モーダル
      show: false,
      // フォームチェック
      validated: false,
    };
  }

  // モーダル設定
  handleClose = async () => {
    await this.setState({ show: false });

    // ページリロード
    document.location.reload();
  }

  handleShow = async () => this.setState({ show: true });

  componentDidMount = async () => {
    try {
      const web3 = await getWeb3();

      const accounts = await web3.eth.getAccounts();
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = Resister.networks[networkId];
      const instance = new web3.eth.Contract(
        Resister.abi,
        deployedNetwork && deployedNetwork.address
      );

      this.setState({ web3, accounts, contract: instance });
    } catch (error) {
      alert(
        `Failed to load web3, accounts, or contract. Check console for details.`
      );
      console.error(error);
    }

    const { accounts, contract } = this.state;
    console.log(accounts);

    const item = await contract.methods.accounts(accounts[0]).call();
    this.state.lines.push({
      item
    });

    console.log(this.state.lines);
  };

  // アカウント情報の登録
  writeRecord = async () => {
    const { accounts, contract, name, age, hobby } = this.state;
    const result = await contract.methods.registerAccount(name, age, hobby).send({
      from: accounts[0],
    });
    console.log(result);

    if (result.status === true) {
      this.handleShow();
    }
  };

  // アカウント情報の読み込み
  viewRecord = async () => {
    const { contract, accounts } = this.state;
    console.log(contract);

    const result = await contract.methods.viewAccount(accounts[0]).call();
    console.log(result);

    const outputName = result[0];
    const outputAge = result[1];
    const outputHobby = result[2];
    this.setState({ outputName, outputAge, outputHobby });
  };

  handleChange = (name) => (event) => {
    this.setState({ [name]: event.target.value });
  };

  // フォーム最終確認
  handleSubmit = (event) => {
    const form = event.currentTarget;
    if (form.checkValidity() === false) {
      event.preventDefault();
      event.stopPropagation();
    }
    this.setState({ validated: true });
  };


  render() {
    return (
      <div className="App">
        <Row className="text-left m-5">
          <Col md={{ span: 4, offset: 2 }}>
            <Form className="justify-content-center"
              noValidate validated={this.state.validated} >

              <Form.Group controlId="validationCustom03">
                <Form.Label>Name</Form.Label>
                <Form.Control
                  type="name"
                  onChange={this.handleChange("name")}
                  placeholder="Enter Name"
                  required />
                <Form.Control.Feedback type="invalid">
                  Please enter name.
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group controlId="validationCustom03">
                <Form.Label>Age</Form.Label>
                <Form.Control
                  type="text"
                  onChange={this.handleChange("age")}
                  placeholder="Enter Age"
                  required />
                <Form.Text className="text-muted">
                  We'll never share your age with anyone else.
                </Form.Text>
                <Form.Control.Feedback type="invalid">
                  Please enter age.
                </Form.Control.Feedback>
              </Form.Group>

              <Form.Group controlId="validationCustom03">
                <Form.Label>Hobby</Form.Label>
                <Form.Control
                  type="text"
                  onChange={this.handleChange("hobby")}
                  placeholder="Enter Hobby"
                  required />
                <Form.Control.Feedback type="invalid">
                  Please enter hobby.
                </Form.Control.Feedback>
              </Form.Group>

              {/* フォームチェック */}
              <Form.Group>
                <Form.Check
                  required
                  label="Agree to terms and conditions"
                  feedback="You must agree before submitting."
                  onChange={this.handleSubmit}
                />
              </Form.Group>

              <Button variant="primary" type="button" onClick={this.writeRecord}>
                会員登録
              </Button>

              {/* モーダル */}
              <Modal show={this.state.show} onHide={this.handleClose}>
                <Modal.Header closeButton>
                  <Modal.Title>Wellcome to BcDiary!!</Modal.Title>
                </Modal.Header>
                <Modal.Body>会員登録が完了しました。</Modal.Body>
                <Modal.Footer>
                  <Button variant="secondary" onClick={this.handleClose}>
                    Close
                  </Button>
                </Modal.Footer>
              </Modal>
            </Form>
          </Col>

          <Col md={{ span: 4, offset: 0 }} className='ml-5'>
            <Form className="justify-content-center">
              <Form.Group controlId="formBasicEmail">
                <Form.Label>検索したいアドレスを入力してください。</Form.Label>
                <Form.Control onChange={this.handleChange("address")}
                  placeholder="Search" />
              </Form.Group>
              <Button variant="primary" type="button" onClick={this.viewRecord}>
                閲覧
          </Button>
            </Form>

            <br />
            <br />

            {this.state.outputName ? <p>Name: {this.state.outputName}</p> : <p></p>}
            {this.state.outputAge ? <p>Age: {this.state.outputAge}</p> : <p></p>}
            {this.state.outputHobby ? <p>Hobby: {this.state.outputHobby}</p> : <p></p>}

          </Col>
        </Row>
      </div >
    );
  }
}

export default App;
```

#DApp完成！
上記のプログラムの元、完成したアプリケーションはこのようになります。
[Github](https://github.com/sho11hei12-1998/ResisterApp)にソースコードをあげているのでそちらも良ければ参考にして下さい。

![スクリーンショット 2020-11-29 11.29.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526953/ec6596dc-5bf1-8a96-d185-c8924806697b.png)


#最後に
ここまで記事を最後まで読んで頂いて本当にありがとうございます！そしてお疲れ様です！
初めてDApps開発をする方にとって少しでも力になっていれば幸いです。
是非LGTMボタンをポチッとお願いします！！