【React初心者】 #3 ルーティング・ページ遷移を作る！ react-router-dom

# Reactでreact-router-domのルーティング設定
３回目の今回は、**Reactでreact-router-dom**
を使用したルーティングを使います。
URLを変えることで、表示するページを変える（レンダリングする）ことができます。

前回までで、

- Material-UIで最低限のデザインを一から作成
- データをAPIから取得して表示

しました。コードは前回の続きとなります。

# :pushpin: React & Material UI & Django RestFramework記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[React Material UIを使ってみる](https://qiita.com/Bashi50/items/8964cc55c596e51fcbbe)|
|**part2**|[【初心者】#2 React axiosでAPI データ取得](https://qiita.com/Bashi50/items/b4e0626b2ede73568eab)|
|**part3**|**<font color="red">【React】 #3 ルーティング・ページ遷移 react-router-dom</font>　← ココ**|
|**part4**|[【Python】 Django REST Framework ReactとAPIサーバー繋ぐ](https://qiita.com/Bashi50/items/203505440049d759b694)|

# やること

- react-router-domでのReactでのルーティング
- ルーティングするためのリンクの付け方


# react-router-dom導入
まず、インストールが必要です。
今回は`react-router-dom： 5.2.0`を使用しています。

```
$ npm install react-router-dom
```
TypeScriptの場合は`npm install react-router-dom @types/react-router-dom`みたいです。


とりあえず、aboutページを作成します。
ただ、文字を表示するだけにします。

ということで、Aboutコンポーネントを作成。

```
$ touch src/components/About.js
```

文字を表示するだけになります。

```src/components/About.js
import React from 'react'

function About() {
    return (
        <div>
            aboutページ
        </div>
    )
}

export default About
```

前回までのコードにRouterとSwitch、Routeを足して、
URLによって、レンダリングするコンポーネントを変えています。

```material-react/src/App.js
import './App.css';
import { Grid } from '@material-ui/core';
import Header from './components/Header';
import Content from './components/Content';
import About from './components/About';

import {
  BrowserRouter as Router,
  Switch,
  Route
} from "react-router-dom";

function App() {
  return (
    <Grid container direction="column">
      <Grid item>
        <Header />
      </Grid>
      <Grid item container>
        <Grid sm={2} />
        <Grid xs={12} sm={8}>
        <Router>
          <Switch>
            <Route exact path="/">
              <Content />
            </Route>
            <Route path="/about">
              <About />
            </Route>
          </Switch>
        </Router>
        </Grid>
        <Grid sm={2} />
      </Grid>
    </Grid>
  );
}

export default App;
```

> http://localhost:3000/about

とURLに入れると、

![スクリーンショット 2021-01-16 5.25.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/c7b3e011-5695-13fc-e866-fb404f780aac.png)


文字だけのAboutコンポーネントが表示されています。

## 説明と注意
`<Route exact path="/">`の`exact`は完全に一致しないとダメという意味です。

一致するURLを上から探すのだけど、このexactをつけないと、`http://localhost:3000/jasdklfs`とか意味ないものやでもContent`コンポーネントを使うことになります。

というか、`http://localhost:3000/`移行にURLに、何があろうが`Content`コンポーネントを使います。

試しに、exactを消して/aboutにアクセスしてみれば、`Content`コンポーネントが出るので、試してみてもいいと思います。
`<Route path="/">`　変更後、
`http://localhost:3000/jasdklfs`と`http://localhost:3000/about`
と書いてみましょう。

どちらも、Contentコンポーネントの内容が表示されます。
<font color="red">**「書く順番」**</font>を考えましょう。


# URLのパラメータを取得して個別記事表示
前回、JSON Placeholderからデータを取得しました。

APIをみると、個別記事の取得もできるので、
記事についている「詳細を見る」ボタンを押したら遷移させます。

## 個別記事のコンポーネントを作成 

前回までで、左側は作りました。
今回は、右側を作ります。
個別記事IDにアクセスしたときにPostContentコンポーネントをレンダリングすることになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/ea138e50-3e8b-22a1-caeb-aef0fc22a92a.png)


```
$ touch src/components/PostContent.js
```

`/post/1`だったら記事１を表示するようにしてみます。とりあえず
idを受け取れるかテスト。
`/post/1`にアクセスしたら`useParams()`に`{id: 1}`のようなデータが入るので`id`だけをとるため`const { id }`にしています。

そして、個別記事の後に受け取ったIDをくっつけて表示します。

```material-react/src/components/PostContent.js
import React from 'react'
import { useParams } from 'react-router-dom'

function PostContent() {
    const { id } = useParams();
    return (
        <div>
            <h1>個別記事 {id}</h1>
        </div>
    )
}

export default PostContent
```

![スクリーンショット 2021-01-16 6.02.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/bfcd7a09-3dc5-85f5-c0e8-8d6afc813bcd.png)

`http://localhost:3000/post/1`にアクセスしたら`1`が表示されているのでOK
`http://localhost:3000/post/１０`とかにしたら`10`に変わりますので試してみてください。

# 個別記事データをAPIで取得
Getで取得しましょう。URLの最後に記事のIDを入れればデータを取得できます。

では、JSON PlaceholderでAPIのURLは

> https://jsonplaceholder.typicode.com/posts/1


```.json
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
```

axiosを使ったAPIのデータ取得は前回やったので簡単に書きます。

```material-react/src/components/PostContent.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { useParams } from 'react-router-dom'

import { Grid } from '@material-ui/core'
import BodyCard from './BodyCard'


const cardContent = 
    {
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    }

function PostContent() {
    const { id } = useParams();
    const [post, setPosts] = useState([])

    useEffect(() => {
        axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
        .then(res => {
            setPosts(res.data)
        })
    }, [])

    return (
        <Grid container spacing={2}>
            <Grid item xs={12} key={post.id}>
                <BodyCard {...{...post, ...cardContent}} />
            </Grid>
        </Grid>
    )
}

export default PostContent
```

- `https://jsonplaceholder.typicode.com/posts/${id}`でURLから取得したパラメータを埋め込んでGET、APIでの取得
- useStateを使って、ページを開いた時一度だけ、APIで取得
- 前回作った、`BodyCard`コンポーネントに渡してMaterial-UIのカードの形式で作成
  - `{...{...post, ...cardContent}}`で二つのオブジェクトを結合して渡してます

とりあえず、ページを作り込むより、ルーティングの使い方説明メインなので、

- 詳細をみるというボタンがあったり、
- 画像がAPIでランダム取得するので変わってしまう、
- 画像がhight: 150pxだと大きさおかしい

とかツッコミありますができました。
![スクリーンショット 2021-01-16 16.44.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/c48a6cd6-36bb-48da-2ed6-842f8ebabd1b.png)

一応、前回の記事から変更はないですが、BodyCardコンポーネントも載せておきます。

```BodyCard.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import CardHeader from '@material-ui/core/CardHeader';
import Avatar from '@material-ui/core/Avatar';
import IconButton from '@material-ui/core/IconButton';
import StarBorderOutlinedIcon from '@material-ui/icons/StarBorderOutlined';
import { CardMedia } from '@material-ui/core';

const useStyles = makeStyles({
    bullet: {
      display: 'inline-block',
      margin: '0 2px',
      transform: 'scale(0.8)',
    },
    title: {
      fontSize: 14,
    },
    pos: {
      marginBottom: 12,
    },
    cHeader: {
        height: '50px',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        "& .MuiCardHeader-content": {
            overflow: 'hidden'
        }
    },
    cContent: {
        height: '200px',
        overflow: 'hidden'
    }
});


function BodyCard(props) {
    const { userId, id, title, body, avatarUrl, imageUrl } = props;
    const classes = useStyles();
    const bull = <span className={classes.bullet}>•</span>;
    return (
        <Card variant="outlined">
            <CardHeader
                avatar={<Avatar src={avatarUrl} />}
                action={
                <IconButton aria-label="settings">
                    <StarBorderOutlinedIcon />
                </IconButton>
                }
                className={classes.cHeader}
                title={title}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent className={classes.cContent}>
            <Typography variant="body2" component="p">
                {body}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard
```

# ボタンにリンクをつける
Material-UIにはButtonにhrefつければ遷移できるようになります。
以下のようにすれば大丈夫。

```BodyCard.js
<Button size="small" href={`/post/${id}`}>詳細をみる</Button>
```

今回、Buttonにリンク機能がついていたので使いましたが、
デザインを自分でやりたい、文字にリンクつけるなどの場合は、

```.js
<Link to="/post/1">記事1の詳細</Link>

<Link to=`/post/${id}`>記事{$id}の詳細</Link>
```

などにすればいいと思います。
react-routerの公式サイトは英語ですが、
コードがみやすいのでみながら試してみてください。

> https://reactrouter.com/web/example/basic


# まとめ

- react-router-domでのReactでのルーティング
- ルーティングするためのリンクの付け方

をやりました。次回はAPIサーバーをPythonの**Django**で自作します。


## 補足
これまで、RouterをApp.jsに書きました。
ただ、ヘッダーがいらない、デザインを変えたいなどの場合（ログインページ、LP、認証のためのURLとか使う場合など）、

`index.js`にrouterを書くこともできるので補足として。

```index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import About from './components/About';
import reportWebVitals from './reportWebVitals';
import { Route, BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <div>
        <Route exact path="/" component={App} />
        <Route exact path="/about" component={About} />
      </div>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);
```

# :pushpin: React & Material UI & Django RestFramework記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[React Material UIを使ってみる](https://qiita.com/Bashi50/items/8964cc55c596e51fcbbe)|
|**part2**|[【初心者】#2 React axiosでAPI データ取得](https://qiita.com/Bashi50/items/b4e0626b2ede73568eab)|
|**part3**|**<font color="red">【React】 #3 ルーティング・ページ遷移 react-router-dom</font>　← ココ**|
|**part4**|[【Python】 Django REST Framework ReactとAPIサーバー繋ぐ](https://qiita.com/Bashi50/items/203505440049d759b694)|