로메오의 블로그

[DApp - react.js] Election 화면을 React.js로 변경 - back-end 연결 본문

Backend/Python & Blockchain

[DApp - react.js] Election 화면을 React.js로 변경 - back-end 연결

romeoh 2019. 7. 8. 06:58
반응형

App.js에서 truffle과 연결하도록 하겠습니다.

/src/js/App.js

계정 정보를 가져와서 Content.js 콤포넌트로 넘겨서 화면에 표시하도록 하겠습니다.

import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import Election from '../../build/contracts/Election.json'
import Content from './Content'

class App extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            
            // 계정 정보를 저장함
            account: '0x0',
        }

        // web3Provider 생성하기
        if (typeof web3 != 'undefined') {
            this.web3Provider = web3.currentProvider
        } else {
            this.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545')
        }
      
        // web3 생성
        this.web3 = new Web3(this.web3Provider)
    }

    componentDidMount() {

        // 계정 정보를 가지고 온다.
        this.web3.eth.getCoinbase((err, account) => {

            // 계정 정보를 state에 저장함
            this.setState({ account })
            console.log(account)
        })
    }

    render() {
        return (
            <React.Fragment>
                <h1>투표 결과</h1>

                {/* <!-- 로딩바 --> */}
                <div id="loader">
                    <p>로딩중...</p>
                </div>
            
                {/* Content compoent를 추가함 */}
                <Content
                    // 계정정보를 Content에 props로 넘겨줌
                    account={this.state.account}
                />

            </React.Fragment>
        )
    }
}

ReactDOM.render(
   <App />,
   document.querySelector('#root')
)

 

src/js/Content.js

import React from 'react'
import Table from './Table'
import Form from './Form'

export default class Content extends React.Component {
    render() {
        return (
            <div id="content">

                {/* <!-- 투표결과 표시 --> */}
                <Table />

                {/* <!-- 투표하기 --> */}
                <Form />

                {/* <!-- 계정 정보 표시 --> */}
                <p>Your Account: {this.props.account}</p>
            </div>
        )
    }
}

화면에 계정 정보가 표시됩니다.

 

후보자 목록 가지고 오기

/src/js/App.js

import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import Election from '../../build/contracts/Election.json'
import Content from './Content'

class App extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            
            // 계정 정보를 저장함
            account: '0x0',

            // 후보자 목록을 저장함
            candidates: []
        }

        // web3Provider 생성하기
        if (typeof web3 != 'undefined') {
            this.web3Provider = web3.currentProvider
        } else {
            this.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545')
        }
      
        // web3 생성
        this.web3 = new Web3(this.web3Provider)

        // truffle 계약 생성
        this.election = TruffleContract(Election)
        this.election.setProvider(this.web3Provider)
    }

    componentDidMount() {

        // 계정 정보를 가지고 온다.
        this.web3.eth.getCoinbase((err, account) => {

            // 계정 정보를 state에 저장함
            this.setState({ account })
            
            // 계약 정보를 가지고 온다.
            this.election.deployed().then(electionInstance => {
                this.electionInstance = electionInstance

                // 후보자 Count를 가지고 온다.
                this.electionInstance.candidatesCount().then((candidatesCount) => {
                    for (var i = 1; i <= candidatesCount; i++) {
                        this.electionInstance.candidates(i).then((candidate) => {
                            const candidates = [...this.state.candidates]
                            candidates.push({
                                id: candidate[0],
                                name: candidate[1],
                                voteCount: candidate[2]
                            });

                            // 후보자 목록을 저장함
                            this.setState({ candidates: candidates })
                        });
                    }
                })
            })
        })
    }

    render() {
        return (
            <React.Fragment>
                <h1>투표 결과</h1>

                {/* <!-- 로딩바 --> */}
                <div id="loader">
                    <p>로딩중...</p>
                </div>
            
                {/* Content compoent를 추가함 */}
                <Content
                    // 계정정보를 Content에 props로 넘겨줌
                    account={this.state.account}
                    // 후보자 목록을 Content에 props로 넘겨줌
                    candidates={this.state.candidates}
                />

            </React.Fragment>
        )
    }
}

ReactDOM.render(
   <App />,
   document.querySelector('#root')
)

state에 candidates: [] 를 추가합니다.

"// truffle 계약 생성" 부분에 this.election 객체를 생성하고

"// 계약 정보를 가지고 온다." 부분을 추가해서 candidates에 후보자 목록을 저장합니다.

Content 콤포넌트에 candidates props로 넘겨줍니다.

 

 

/src/js/Content.js

import React from 'react'
import Table from './Table'
import Form from './Form'

export default class Content extends React.Component {
    render() {
        return (
            <div id="content">

                {/* <!-- 투표결과 표시 --> */}
                <Table 
                    candidates={this.props.candidates}
                />

                {/* <!-- 투표하기 --> */}
                <Form 
                    candidates={this.props.candidates}
                />

                {/* <!-- 계정 정보 표시 --> */}
                <p>Your Account: {this.props.account}</p>
            </div>
        )
    }
}

props로 받은 candidates 를 Table과 Form에 다시 전달합니다.

 

/src/js/Table.js

import React from 'react'

export default class Content extends React.Component {
    render() {
        return (
            <React.Fragment>
                {/* <!-- 투표결과 표시 --> */}
                <table>
                    <colgroup>
                        <col width="60" />
                        <col width="*" />
                        <col width="100" />
                    </colgroup>
                    <thead>
                        <tr>
                            <th scope="col">기호</th>
                            <th scope="col">이름</th>
                            <th scope="col">득표수</th>
                        </tr>
                    </thead>
                    <tbody id="candidatesResults">
                        {this.props.candidates.map((candidate, key) => {
                            return (
                                <tr key={key}>
                                    <th>{candidate.id.toNumber()}</th>
                                    <td>{candidate.name}</td>
                                    <td>{candidate.voteCount.toNumber()}</td>
                                </tr>
                            )
                        })}
                    </tbody>
                </table>
                <hr />
            </React.Fragment>
        )
    }
}

Content.js에서 전달받은 candidates props를 loop 돌려서 table을 완성 합니다.

 

/src/js/Form.js

import React from 'react'

export default class Content extends React.Component {
    render() {
        return (
            <React.Fragment>
                {/* <!-- 투표하기 --> */}
                <h2>투표 참여</h2>
                <select>
                    <option value="">후보자를 선택하세요.</option>
                    {this.props.candidates.map((candidate, key)=> {
                        return <option value={candidate.id} key={key}>{candidate.name}</option>
                    })}
                </select>
                <button>투표하기</button>
                <hr />
            </React.Fragment>
        )
    }
}

 

후보자 목록과 투표 참여 부분에 후보자가 표시됩니다.

 

로딩중 메세지 처리하기

/src/js/App.js

import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import Election from '../../build/contracts/Election.json'
import Content from './Content'

class App extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            
            ....

            // 로딩중 메세지 표시
            loading: true,
        }

        ....
    }

    componentDidMount() {

        // 계정 정보를 가지고 온다.
        this.web3.eth.getCoinbase((err, account) => {

            ....
            
            // 계약 정보를 가지고 온다.
            this.election.deployed().then(electionInstance => {
                ....

                // 후보자 Count를 가지고 온다.
                this.electionInstance.candidatesCount().then((candidatesCount) => {
                    for (var i = 1; i <= candidatesCount; i++) {
                        this.electionInstance.candidates(i).then((candidate) => {
                            ....

                            // 후보자 목록을 저장함
                            this.setState({ 
                                candidates: candidates,
                                loading: false
                            })
                        });
                    }
                })
            })
        })
    }

    render() {
        return (
            <React.Fragment>
                ....

                { this.state.loading
                    ? <p>로딩중...</p>
                    : <Content
                        // 계정정보를 Content에 props로 넘겨줌
                        account={this.state.account}
                        // 후보자 목록을 Content에 props로 넘겨줌
                        candidates={this.state.candidates}
                    />
                }
            </React.Fragment>
        )
    }
}

ReactDOM.render(
   <App />,
   document.querySelector('#root')
)

state에 loading을 추가했습니다.

계약정보를 읽어오면 loading에 false를 넣어줍니다.

render에서 this.state.loading 상태에 따라 로딩중 메세지와 Content를 선별해서 표시해줍니다.

전체 코드는 아래와 같습니다.

import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import Election from '../../build/contracts/Election.json'
import Content from './Content'

class App extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            
            // 계정 정보를 저장함
            account: '0x0',

            // 후보자 목록을 저장함
            candidates: [],

            // 로딩중 메세지 표시
            loading: true,
        }

        // web3Provider 생성하기
        if (typeof web3 != 'undefined') {
            this.web3Provider = web3.currentProvider
        } else {
            this.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545')
        }
      
        // web3 생성
        this.web3 = new Web3(this.web3Provider)

        // truffle 계약 생성
        this.election = TruffleContract(Election)
        this.election.setProvider(this.web3Provider)
    }

    componentDidMount() {

        // 계정 정보를 가지고 온다.
        this.web3.eth.getCoinbase((err, account) => {

            // 계정 정보를 state에 저장함
            this.setState({ account })
            
            // 계약 정보를 가지고 온다.
            this.election.deployed().then(electionInstance => {
                this.electionInstance = electionInstance

                // 후보자 Count를 가지고 온다.
                this.electionInstance.candidatesCount().then((candidatesCount) => {
                    for (var i = 1; i <= candidatesCount; i++) {
                        this.electionInstance.candidates(i).then((candidate) => {
                            const candidates = [...this.state.candidates]
                            candidates.push({
                                id: candidate[0],
                                name: candidate[1],
                                voteCount: candidate[2]
                            });

                            // 후보자 목록을 저장함
                            this.setState({ 
                                candidates: candidates,
                                loading: false
                            })
                        });
                    }
                })
            })
        })
    }

    render() {
        return (
            <React.Fragment>
                <h1>투표 결과</h1>

                { this.state.loading
                    ? <p>로딩중...</p>
                    : <Content
                        // 계정정보를 Content에 props로 넘겨줌
                        account={this.state.account}
                        // 후보자 목록을 Content에 props로 넘겨줌
                        candidates={this.state.candidates}
                    />
                }
            </React.Fragment>
        )
    }
}

ReactDOM.render(
   <App />,
   document.querySelector('#root')
)

이제 브라우저에서 로딩중 메세지가 사라지고 목록이 표시됩니다.

 

투표하기

/src/js/App.js

class App extends React.Component {

    constructor(props) {
        ....

        // castVote 이벤트를 바인딩 한다.
        this.castVote = this.castVote.bind(this)
    }

    ....
    
    // 투표하기 
    castVote(candidateId) {
        this.setState({ loading: true })
        this.electionInstance.vote(candidateId, { from: this.state.account })
            .then(result => {
                this.setState({ loading: false })
            })
    }

    render() {
        return (
            <React.Fragment>
                <h1>투표 결과</h1>

                { this.state.loading
                    ? <p>로딩중...</p>
                    : <Content
                        ....
                        // 투표하지 함수를 props로 넘겨줌
                        castVote={this.castVote}
                    />
                }
            </React.Fragment>
        )
    }
}

App.js 파일에 위와 같이 코드를 추가합니다.

전체코드는 아래와 같습니다.

import React from 'react'
import ReactDOM from 'react-dom'
import Web3 from 'web3'
import TruffleContract from 'truffle-contract'
import Election from '../../build/contracts/Election.json'
import Content from './Content'

class App extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            
            // 계정 정보를 저장함
            account: '0x0',

            // 후보자 목록을 저장함
            candidates: [],

            // 로딩중 메세지 표시
            loading: true,
        }

        // web3Provider 생성하기
        if (typeof web3 != 'undefined') {
            this.web3Provider = web3.currentProvider
        } else {
            this.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545')
        }
      
        // web3 생성
        this.web3 = new Web3(this.web3Provider)

        // truffle 계약 생성
        this.election = TruffleContract(Election)
        this.election.setProvider(this.web3Provider)

        // castVote 이벤트를 바인딩 한다.
        this.castVote = this.castVote.bind(this)
    }

    componentDidMount() {

        // 계정 정보를 가지고 온다.
        this.web3.eth.getCoinbase((err, account) => {

            // 계정 정보를 state에 저장함
            this.setState({ account })
            
            // 계약 정보를 가지고 온다.
            this.election.deployed().then(electionInstance => {
                this.electionInstance = electionInstance

                // 후보자 Count를 가지고 온다.
                this.electionInstance.candidatesCount().then((candidatesCount) => {
                    for (var i = 1; i <= candidatesCount; i++) {
                        this.electionInstance.candidates(i).then((candidate) => {
                            const candidates = [...this.state.candidates]
                            candidates.push({
                                id: candidate[0],
                                name: candidate[1],
                                voteCount: candidate[2]
                            });

                            // 후보자 목록을 저장함
                            this.setState({ 
                                candidates: candidates,
                                loading: false
                            })
                        });
                    }
                })
            })
        })
    }

    // 투표하기 
    castVote(candidateId) {
        this.setState({ loading: true })
        this.electionInstance.vote(candidateId, { from: this.state.account })
            .then(result => {
                this.setState({ loading: false })
            })
    }

    render() {
        return (
            <React.Fragment>
                <h1>투표 결과</h1>

                { this.state.loading
                    ? <p>로딩중...</p>
                    : <Content
                        // 계정정보를 Content에 props로 넘겨줌
                        account={this.state.account}
                        // 후보자 목록을 Content에 props로 넘겨줌
                        candidates={this.state.candidates}
                        // 투표하지 함수를 props로 넘겨줌
                        castVote={this.castVote}
                    />
                }
            </React.Fragment>
        )
    }
}

ReactDOM.render(
   <App />,
   document.querySelector('#root')
)

/src/js/Content.js

import React from 'react'
import Table from './Table'
import Form from './Form'

export default class Content extends React.Component {
    render() {
        return (
            <div id="content">

                {/* <!-- 투표결과 표시 --> */}
                <Table 
                    candidates={this.props.candidates}
                />

                {/* <!-- 투표하기 --> */}
                <Form 
                    candidates={this.props.candidates}
                    castVote={this.props.castVote}
                />

                {/* <!-- 계정 정보 표시 --> */}
                <p>Your Account: {this.props.account}</p>
            </div>
        )
    }
}

Form 콤포넌트에 castVote props를 넘겨줍니다.

 

/src/js/Form.js

import React from 'react'

export default class Content extends React.Component {
    render() {
        return (
            <React.Fragment>
                {/* <!-- 투표하기 --> */}
                <h2>투표 참여</h2>
                
                <select
                     ref={input => this.candidateId = input}
                >
                    <option value="">후보자를 선택하세요.</option>
                    {this.props.candidates.map((candidate, key)=> {
                        return <option value={candidate.id} key={key}>{candidate.name}</option>
                    })}
                </select>

                <button
                    onClick={event => {
                        event.preventDefault()
                        this.props.castVote(this.candidateId.value)
                    }}
                >
                    투표하기
                </button>

                <hr />
            </React.Fragment>
        )
    }
}

select에 ref를 추가하고 button에 onClick 이벤트를 추가해서 castVote 함수를 호출합니다.

 

투표하기

$ truffle migrate --reset

로컬 블록체인을 reset 해줍니다.

투표하기 하면 MetaMask에서 결제를 승인하고 투표가 완료 됩니다.

 

브라우저를 새로고침 하면 투표결과가 반영된 것을 확인 할 수 있습니다.

반응형
Comments