React + Rails のSPAでshowアクションを実装する

## これは何？
React+Railsで簡単なタスク管理アプリを作っています。
一覧画面からタスク名のリンクをクリックすると、詳細画面が現れます。

[![Image from Gyazo](https://i.gyazo.com/d14ee05380038758d2a8d4934b0a5ddf.gif)](https://gyazo.com/d14ee05380038758d2a8d4934b0a5ddf)

実行環境は以下の通りです。

- Rails 6.0.3
- React 17.0.2

また、今回のディレクトリ構成は以下の通りです。(関係のある箇所だけ表示)

```
.
├── controllers
│   └── api
│       └── tasks_controller.rb
└── javascript
    └── pages
        ├── Task.jsx
        └── Tasks.jsx
```

## Rails側

spaではないRailsのアプリとほぼ変わりありません。`/api`配下に置いたコントローラーに`index`(実装については[前回の記事](https://qiita.com/tanutanu/items/5a8dcbdbedc0ec33b4e9)をご参照ください)と`show`アクションを記載します。

`render json: ...`でJSON形式でビューを描画します。

```ruby:app/controllers/api/tasks_controller.rb
class Api::TasksController < ApplicationController
  def index
    @tasks = current_user.tasks
    render json: @tasks
  end

  def show
    @task = current_user.tasks.find(params[:id])
    render json: @task
  end
end
```

## SPA側（一覧表示画面）

タスク一覧表示画面(`Tasks`コンポーネント）に実際に書いたコードがこちらです。

```js:app/javascript/pages/Tasks.jsx
import React, {useState} from 'react'
import { Link, withRouter } from 'react-router-dom'
import axios from 'axios'

export const Tasks = withRouter(() => { // ★2解説します
  const [tasks, setTasks] = useState([])

  React.useEffect(async () =>{
    const response = await axios.get('/api/tasks');
    setTasks(response.data)
  }, [])

  return (
    <div>
      <h1>タスク一覧</h1>
      <table>
        // 中略
        <tbody>
          {tasks.map(task => (
            <tr key={task.id}>
              <td>
                <Link to={{ // ★１解説します
                  pathname: "/spa/tasks/" + task.id,
                  state: {id: task.id},
                  }}>{task.name}</Link>
              </td>
              // 中略
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
})
```

### react-router と react-router-dom

まず、調べていて迷ったのが、`react-router`と`react-router-dom`があるっぽいということ。その点は、[こちらの記事](https://qiita.com/koja1234/items/486f7396ed9c2568b235)が非常に丁寧に解説してくれていました。

- [react-routerとreact-router-domの違い](https://qiita.com/koja1234/items/486f7396ed9c2568b235)

要するに、`react-router-dom`の方が新しくて使い勝手も良いそうなので、`react-router-dom`を使いましょう、とのことでした。

### Link to
ポイントの一つ目はこの部分(★１)で、

```js:
<Link to={{
  pathname: "/spa/tasks/" + task.id,
  state: {id: task.id},
}}>{task.name}</Link>
```

`react-router-dom`の`Link`という関数で、遷移先や遷移先のページに送るデータなどが指定できます。詳しくは[公式のAPIドキュメント](https://reactrouter.com/web/api/Link)がわかりやすかったですが、

- [Link](https://reactrouter.com/web/api/Link)

上記を指定することで、受取手側のコンポーネントでは`locatiton`で以下のような情報が取得できました。

```js
console.log(location)
=> {pathname: "/spa/tasks/1", state: {id: 1}, search: "", hash: "", key: "5ox837"}
```

### withRouter

ただし、上記だけでは`location`の`state`が`undefined` になります。調べてみると[withRouterでラップしてね](https://stackoverflow.com/questions/56849841/why-is-state-undefined-when-passed-through-react-router-link-component)という回答がたくさん出てきたので、今回も以下のように全体をラップしてみました（★２）。

```js
export const Tasks = withRouter(() => {
 // 略
})
```
[公式のドキュメント](https://reactrouter.com/core/api/withRouter)もみてみると、

- [withRouter](https://reactrouter.com/core/api/withRouter)

>  withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.

(withRouterは、レンダリングされるたびに、更新されたmatch、location、およびhistoryのpropsをラップされたコンポーネントに渡します。)

とのことでした。Functional Componentでの`withRouter`の使い方は、こちらを参考にしました。

- [Functional ComponentでもReact RouterのwithRouterを使う方法](https://qiita.com/yanchi4425/items/934cfbd644ace4834abc)

## 完成
以上で、実装は終わりになります。
まさかShowアクションでここまで調べることになるとは思わなかった...^^
本日はDestroyアクションも試してみたのですが、そちらはまた別の機会に記事にしたいです。

ではでは。