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

## React Routerの概要

[React Router](https://reacttraining.com/react-router/)はページ遷移などを制御するためのもので、SPAを作る際には必須となる機能です。

vte.cxのblankプロジェクトでも使用していますが、いくつかハマリどころがあったので、ここにまとめておきます。また、[初心者歓迎！React初心者勉強会とLT大会](https://vtecx.connpass.com/event/67541/)でも紹介します。

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

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

 * demo.js

```javascript
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

```javascript
	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にするだけです。

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

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

React/Typescriptによる業務システム開発にご興味のある方は、[こちらの記事](https://blog.vte.cx/%E3%82%A8%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%97%E3%83%A9%E3%82%A4%E3%82%BA%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%81%AE%E5%AE%9F%E9%9A%9B-a2fea2980ec5)もぜひ読んでみてください。