EthereumコントラクトとReactの連携

##追記 2020/3/4
web3のアップデートに対応し、TypeScript + React + Redux + Web3 + truffle + React-routerのボイラープレートを作成しました。[ここ](https://github.com/andynuma/truffle-typescript-react-redux-box)にあります。![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189458/60c9c36b-69b7-5e36-20bb-2cfc29521647.gif)


##追記 2019/12/29
本記事よりも[React Truffe Box](https://github.com/truffle-box/react-box)の方が非常に優秀です。

#この記事について
**solidityでコントラクトは書けるけど，結局どうやってフロントエンドと繋げればいいの？**
といった疑問を少しでも解決できたらいいなと思い，自身の体験を書いてみます．


#開発環境
現在のEthereum上で動作しているDaapsで幅広く使用されているフレームワークであるtruffleでの開発を想定します．
フロントエンドは，truffleの公式サイトでも推されているReactを用います．


- [Ethereum](https://www.ethereum.org/)
- [Truffle](https://truffleframework.com/)
- [Ganache 1.1.0](https://truffleframework.com/ganache)
- [remix](https://remix.ethereum.org)
- [infura.io](https://infura.io/)
- [React](https://reactjs.org/)
- [Next.js](https://nextjs.org/)

#手順
##もろもろインストール

- `npm init`
- `npm install --save next react react-dom`
- `npm install truffle-contract --save`

##package.jsonに以下を追記

```jsx:package.json
 {
   // ...
   "scripts": {
     "dev": "next client", 
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   // ...
 }
```

##以下のようにclientフォルダとpagesフォルダなどを作成する．

<img width="354" alt="スクリーンショット 2018-12-19 19.53.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/189458/196a7e4c-6b26-a5cd-9bbe-fe6e61b4075f.png">

##metamaskのチェック

```jsx:client/web3/provider.js
 
 import Web3 from "web3"
 
 const provider = () => {
   // If the user has MetaMask:
   if (typeof web3 !== 'undefined') {
     return web3.currentProvider
   } else {
     console.error("You need to install MetaMask for this app to work!")
   }
 }
 
 export const eth = new Web3(provider()).eth
```


##`client/pages/index.js`を作成

```jsx:client/pages/index.js
 import {eth,getInstance} from "../web3/provider"
 
 export default class IndexPage extends React.Component{
     render() {
         return (
          <h1>Hello</h1>
         )
     }
 }
```
 

 `npm run dev`すると，ブラウザを開いてlocalhost:3030にHelloが表示される．


##`truffle migrate`の後にcontractのjsonファイルをコピーする必要があるので以下を`package.json`に追記

```jsx:package.json
 {
   // ...
   "scripts": {
     "dev": "npm run artifacts && next client",
     "artifacts": "cp -r ./build/contracts/ ./client/web3/artifacts",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   
   // ...
 }
```


`npm run artifacts`を実行．
これで，jsファイルからコントラクトのインスタンスを呼べるようになった．

##インスタンスの呼び方

```jsx:index.js
import { eth, getInstance } from "../web3/provider"
export default class IndexPage extends React.Component {
    async componentDidMount() {
        const storage = await getInstance(Vote) 
        console.log(storage) 
    }
}
```


###`getInstance`は`web3/provider.js`で定義
以下，`getInstance`の定義

```jsx:web3/provider.js 
 export const getInstance = artifact => {
     const contractObj = contract(artifact)
     contractObj.setProvider(provider())
     return contractObj.deployed()
 }
```

###関数を実行するには

- 基本的に`contractInstance.function()`で関数を呼ぶことができる．

 ```jsx:client/pages/index.js  
  import { eth, getInstance } from '../web3/provider'
  import Vote from "../web3/artifacts/Vote.json"
  import {SetVoterAddr} from "../web3/voters"
  
  export default class IndexPage extends React.Component {
    async componentDidMount() {
      const storage = await getInstance(Vote)
      const ownerAddr = await storage.ownerAddr.call()         
      console.log("Owner Address : ",ownerAddr)
    }
  }
```

これで，コントラクトで実装した関数をフロントエンドで呼ぶことができた．

#フォームとの連携
##フォーム
- handleSubmitに`await function(this.state.value)`を付け加える

```jsx:voter.js
 export class SetVoterAddr extends React.Component{
   constructor(props){
     super(props);
     this.state = {value:''};
     this.handleChange = this.handleChange.bind(this);
     this.handleSubmit = this.handleSubmit.bind(this);
   }
 
   async handleChange(event){
     this.setState({value:event.target.value});
   }
 
   async handleSubmit(event){
     console.log("An address was submitted:" + this.state.value);
     event.preventDefault();
     await setVoterAddr(this.state.value)   // <-- add this line
     await test(this.state.value)
   }
 
   render(){
     return(
       <form onSubmit={this.handleSubmit}>
         <label>
           Voter Address:
           <input type="text" value={this.state.value} onChange={this.handleChange}/>
         </label>
         <input type="submit" value="Submit"/>
       </form>
     )
   }
 }
```

###注意点：関数を呼び出すときはアカウントを指定しておかないと`invalid address`になる．
基本的な関数呼び出しの仕方

```jsx
 // eth.getAccounts()でaddressをゲットしておかないといけない
 export const test = async(value)  => {
   const storage = await getInstance(Vote)
   const addresses = await eth.getAccounts()
   const tx = await storage.test(
     value,
   {
     from:addresses[0],
   })
 
   return tx
 }
```
#まとめ
フロントエンドとコントラクトの繋ぎ方は他にもあるのですが，一例としてNext.jsを用いた例を説明しました．
ここまで載せたサンプルコードは，[こちら](https://github.com/andynuma/simple-voting)にあります．