React
vte.cx

React Routerのハマリどころと解決策

More than 1 year has passed since last update.

React Routerの概要

React Routerはページ遷移などを制御するためのもので、SPAを作る際には必須となる機能です。

vte.cxのblankプロジェクトでも使用していますが、いくつかハマリどころがあったので、ここにまとめておきます。また、初心者歓迎!React初心者勉強会とLT大会でも紹介します。

なお、バージョンはv4を使っています。

書き方は概ね以下のように、<Link to="iteminput">で遷移先リンクを指定し、<Switch><Route>で実際に表示するコンポーネントを指定するといった感じになります。

  • demo.js
render() {
    return (
        <Router>
            <div id="wrapper" className={this.state.condition ? 'toggled' :''}>

            <div id="sidebar-wrapper">
                    <ul className="sidebar-nav">
                        <li><Link to="iteminput">入力</Link></li>
                        <li><Link to="listitems">一覧</Link></li>
                        <li><a onClick={ () => this.logout() }>logout</a></li>
                    </ul>
                </div>

                <div id="page-content-wrapper">
                    <Switch>
                        <Route path="/iteminput" component={this.iteminput} />
                        <Route path="/listitems" component={this.listitems} />
                        <Route path="/itemupdate" component={this.itemupdate} />
                        <Route component={this.iteminput} />
                    </Switch>   
                </div>    
            </div>      
        </Router>            
    )
}

ハマリどころ

デフォルト表示をどうするか

まずは簡単なところから。

遷移先を指定するだけではデフォルトの表示で真っ白な画面になってしまいます。
これを回避するには、<Switch><Route>に、path指定のないコンポーネントを書けばよいです。上記の例では、<Route component={this.iteminput} />が該当します。これで、デフォルトでは入力画面が表示されます。

propsを渡せない

これにはハマリました。
実は、<Route component="hoge">で、hogeコンポーネントにpropsを渡すことができません。で、どうするかというと、以下のようにpropsを渡すためのコンポーネントを定義して、それをRouteに指定するということをしなければなりません。ここでは、hideSidemenu関数とhistoryを渡しています。historyは画面遷移の際に必要になるもので次に説明します。

  • demo.js
    listitems = (props) => {
        return (
            <ListItems 
                hideSidemenu={(e)=>this.hideSidemenu(e)} 
                history={props.history}
            />
        )
    }

    iteminput = () => {
        return (
            <ItemInput 
                hideSidemenu={(e)=>this.hideSidemenu(e)} 
            />
        )
    }

    itemupdate = () => {
        return (
            <ItemUpdate 
                hideSidemenu={(e)=>this.hideSidemenu(e)} 
            />
        )
    }

他のコンポーネントからの画面遷移

例えば、一覧画面において行が選択されたら入力画面に遷移させているのですが、これを実行するには以下のようにthis.props.history.push()を使います。ちなみに、this.props.historyは親のコンポーネントから渡されていなければなりません。(上記、ListItemsのコードを参照)

  • demo_listitems.js
    onSelect(e:InputEvent) {
        // 入力画面に遷移
        const itemid = e.currentTarget.id.match(/^\/registration\/(.+),.*$/)
        this.props.history.push('/itemupdate?'+itemid[1])
    }

リロードボタンを押したら404エラーになる

React-router-domのルーターにはBrowserRouterとHashRouterがあります。

BrowserRouterを使うと、path="/iteminput"で実際に遷移するURLがhttp://foo.1.vte.cx/iteminputになるのに対して、HashRouterを使うとhttp://foo.1.vte.cx/demo.html#iteminputとなります。

BrowserRouterの方がキレイなURLになるのですが、リロードボタンを押すとサーバまでリクエスト実行され、そのようなURLにはコンテンツが存在しないので404エラーになります。

一方、HashRouterであれば、demo.htmlがリロードされるだけなので問題なく表示できます。vte.cxでは、HashRouterの方を使うことにします。
切り替えるのは、importするBrowserRouterをHashRouterにするだけです。

import {
//  BrowserRouter as Router,
    Route,
    Link,
    Switch,
    HashRouter as Router
} from 'react-router-dom'

以上、React Routerのハマリどころと解決策でした。