太陽がまぶしかったから

C'etait a cause du soleil.

React 入門 その2 state と fetch による RESTful API の 自動UI 反映

fetch による API コールと動的UI反映

 上記で作成した React Component を拡張した『PHP 初心者による Laravel 入門 その5 RESTful API と MySQL 操作 - 太陽がまぶしかったから』で作成した RESTful API をコールできるようにしたい。非同期のAPIコールを実現するために fetch() を利用することにした。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
    constructor (props) {
        super(props);
        this.state = { companies: [] };
        this.reloadCompanyApi = this.reloadCompanyApi.bind(this);
    }

    reloadCompanyApi() {
        fetch('/api/company', {
            method: 'GET',
            headers: { 'content-type': 'application/json' }, 
        }).then(res => {
            if(res.ok) {
                return res.json();
            } else {
                console.log('error!');
            }
        }).then(json => {
            const companies = json.map(r => { 
                return { 
                    company_id: r.company_id, 
                    company_name: r.company_name 
                };
            });
            companies.sort(function (a, b) { return -(a.company_id - b.company_id); });
            this.setState({ companies: companies });         
        });
    }

    componentWillMount() {
        this.reloadCompanyApi();
        setInterval(this.reloadCompanyApi, '5000');
    }

    render() {
        return (
            <div>
                <h1>会社管理</h1>
                <CompanyList companies={ this.state.companies } />
            </div>
        );
    }
}

class CompanyList extends Component {
    render() {
        const companiesMap = this.props.companies.map(c => {
            return <CompanyItem { ...c } key={ c.company_id } />; 
        });
    
        return (
            <table>
                <thead>
                    <tr><th>会社ID</th><th>会社名</th></tr>
                </thead>
                <tbody>{ companiesMap }</tbody>
            </table>
        );
    }
}

function CompanyItem (props) {
    return (
        <tr>
            <td>{ props.company_id }</td>
            <td>{ props.company_name }</td>
        </tr>
    );
}

if (document.getElementById('app')) {
    ReactDOM.render(<App />, document.getElementById('app'));
}

f:id:bulldra:20180506222425p:plain

 fetch() の利用法については『Fetch API - Web API インターフェイス | MDN』を参照。開始時および 5秒に1回 API から取得した JSON を展開してコンポーネントの state に設定。 React では this.setState() が実行されるたびに、 render() が再起動されるため、DBを更新すればページリロードをせずに動的にUI反映が行われる。

Post によるデータ登録とUI反映

 続いて POST メソッドを呼び出して登録する処理を App クラスに作成する。

class App extends Component {
    constructor (props) {
        super(props);
        this.state = { companies: [] };
        this.reloadCompanyApi = this.reloadCompanyApi.bind(this);
        this.createCompanyApi = this.createCompanyApi.bind(this);
    }

    createCompanyApi(company_name) {
        fetch('/api/company', { 
            method: 'POST',
            headers: { 'content-type': 'application/json' },
            body: JSON.stringify({ company_name: company_name }),
        }).then(res => {
            if(res.ok) {
                return res.json();
            } else {
                console.log('error!');
            }
        }).then(json => {
            this.reloadCompanyApi();
        });
    }

 このメソッドは入力用のコンポーネントから呼び出すため、コンストラクタで bind() を行う。この処理をおこなっておくと、子コンポーネントの属性として関数を受け渡すことができる。

    render() {
        return (
            <div>
                <h1>会社管理</h1>
                <CompanyInput 
                    placeholder={ "会社名" }
                    createCompany={ this.createCompanyApi }
                />
                <CompanyList companies={ this.state.companies } />
            </div>
        );
    }

 CompanyInput コンポーネントではテキストボックスの中身を state に変換して、ボタン押下をトリガに受け渡された createCompany を起動する。

class CompanyInput extends Component {
    constructor (props) {
        super(props);
        this.state = { inputValue:'' };
        this.handleChange = this.handleChange.bind(this);
        this.handleCreateClick = this.handleCreateClick.bind(this);
    }

    handleChange(e) { this.setState({ inputValue: e.target.value }); }
    handleCreateClick() { 
        this.props.createCompany(this.state.inputValue);
        this.setState( { inputValue:'' } );        
    }
    render() {
        return (
            <div>
                <input
                    placeholder={ this.props.placeholder }
                    value={ this.state.inputValue }
                    onChange={ this.handleChange }
                />
                <button onClick={ this.handleCreateClick }>登録</button>
            </div>
        );
    }
}

f:id:bulldra:20180506224931p:plain

 登録処理のレスポンスが戻り次第UIにも動的に反映される。試しに2つのタブを開いて追加した場合でも、もう一方のタブに自動反映される。課題は山積みだけど、Laravel(PHPを含めて) や React を5月から触りはじめて何も分からない状態からなんとなく進捗してきたので、ブラッシュアップしたい。