【初心者】#2 React axiosでAPI データ取得

# APIからJSONデータを取得して表示する
取得前回はMaterial-UIで見た目を整えましたが、
データをハードコードしただけなので、APIからデータを取得してみたいと思います。
useStateを使います。

今後、Python DjangoでAPIサーバー作ってAPIサーバーを作ってみたいと思いますが、最初はJSON Placeholderというサービスで取得したいと思います。

コードは前回の引き続きになります。

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

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

# 使用環境

- axios 0.21.1
- react 17.0.1
- material-ui 4.11.2


# axiosインストールして使う

```
$ npm install axios
```

画面を読み込んだ時に一度だけ、APIでデータを取得するようにします。
まず、必要なものをインポートして

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

`const [post, setPosts] = useState([])`
オブジェクト配列で取得してくるので初期値は配列を入れていきます。

JSONはアクセスしてデータを確認してみてください。
> https://jsonplaceholder.typicode.com/posts

GETで取得するとこのようなデータです。

```.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"
  }
```
のようなデータが取得できます。

# useStateで一回だけ最初にAPI叩いてデータ取得
データは

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

のようにすればGETで取得して、
`const [post, setPosts] = useState([])`
のようにしたので、postに格納されます。

<font color="red">※ 注意ですが、最後の`, []`がないと何度も何度もデータを取得することになるので注意！APIリクエスト回数で課金されるサービスで、これで作ってしまったら大変です</font> 

この空の配列は、ページ開いたら一度だけ。引数をあげなければ、何か処理起こったら毎回動きます。
条件を指定して、処理を実行することもできます。

結果的にこんな感じで処理を書きました。

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

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 Content() {
    const [post, setPosts] = useState([])

    useEffect(() => {
        axios.get('https://jsonplaceholder.typicode.com/posts')
        .then(res => {
            setPosts(res.data)
        })
    }, [])
    const getCardContent = getObj => {
        const bodyCardContent = {...getObj, ...cardContent};
        return (
            <Grid item xs={12} sm={4} key={getObj.id}>
                <BodyCard {...bodyCardContent} />
            </Grid>
        );
    };
    return (
        <Grid container spacing={2}>
            {post.map(contentObj => getCardContent(contentObj))}
        </Grid>
    )
}

export default Content
```

アバターとイメージは[前回](https://qiita.com/Bashi50/items/8964cc55c596e51fcbbe)のままです。
APIで取得したデータをスプレッド演算子
`const bodyCardContent = {...getObj, ...cardContent};`で結合させて、渡しているだけです。


```material-react/src/components/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,
    },
});


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>
                }
                title={title}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent>
            <Typography variant="body2" component="p">
                {body}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard
```

そして、渡されたデータを受け取って、タイトルと、本文を表示に使っています。


![スクリーンショット 2021-01-15 19.28.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/254ef6c1-c757-484f-a555-729082324013.png)

文字数によって、カードの高さずれてますね…
高さ指定、一定文字数以上は消すためにCSS追加します。

## makeStyleの書き換え
Material-UIでCSSを書いてみます。慣れるまでなかなか扱いにくそうですね・・・

```material-react/src/components/BodyCard.js
const useStyles = makeStyles({
.
.
.
    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
```

苦戦しながら、やったらこんな感じでうまくいきました。
`"& .MuiCardHeader-content"`は生成されたクラスで、検証ツールで調べて指定してます。`&`はSCSS的と同じ感じですね。知らないと、ハマりそう。

あと、`textOverflow`はcssだとtext-over-flowですが、`-`をなくして、キャメルケースにする必要があるのでご注意ください。

![スクリーンショット 2021-01-15 20.41.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/de0c799f-0100-cb0c-453a-d69bbda66b5e.png)


# 次回は
次回は、react-router-domを導入してルーティングを設定します。
画像の「詳細をみる」から詳細ページを表示する機能を追加します。

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

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