【React】DogAPIで癒されようPart2

## はじめに
以前、javascript学習も兼ねて[Dog APIというAPIを叩いて癒されよう](https://qiita.com/mk3kohei/items/24f9dd54963124429791)という記事を書きました。
今回はReact学習するにあたり再びDogAPIを使ってみました。

## 今回作成したもの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814555/5335ba24-b91f-0190-3021-0c98afcc409f.png)

### おおまかにやっていることは以下の二つです。
1.画面ロード時に`DogAPI`を叩いてドロップダウンリストに表示する***犬種情報を取得***して表示
2.fetchボタンを押下すると`DogAPI`を叩き、ドロップダウンリストで選択した犬種情報をもつ***画像情報を取得***して表示

## コード全体
おそらくゴミみたいなコードですが一応動くので載せておきます。

<details><summary>コード全体</summary>

```react:Top.jsx
import { ChangeEvent, useEffect, useState } from "react";

export const Top = () => {
  const [dogImage, setDogImage] = useState<string>("");
  const [dogList, setDogList] = useState<Object>({});
  const [selectedDog, setSelectedDog] = useState<string[]>([]);

  //ドロップダウンで犬種を選択したときに情報をsetする
  const handleFormDogList = (event: ChangeEvent<HTMLSelectElement>): void => {
    const selectedDogData: string = event.target.value;
    const breedInfo: string[] = selectedDogData.split(" ");
    return setSelectedDog(breedInfo);
  };

  //fetchボタンを押下したときにAPIをたたいて画像情報をsetする
  const onClickShowDog = () => {
    //splitの関係で分割代入がうまくいかないので分岐して対応
    if (selectedDog.length === 1) {
      const [breed] = selectedDog;
      fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
        .then((response) => {
          return response.json();
        })
        .then((json) => {
          return setDogImage(json["message"]);
        });
    } else {
      const [subBreed, breed] = selectedDog;
      fetch(`https://dog.ceo/api/breed/${breed}/${subBreed}/images/random`)
        .then((response) => {
          return response.json();
        })
        .then((json) => {
          return setDogImage(json["message"]);
        });
    }
  };

  //画面ロード時にドロップダウンリストに入れる情報の取得、画像の初期表示
  const getAllDogList = () => {
    fetch("https://dog.ceo/api/breeds/list/all")
      .then((response) => {
        return response.json();
      })
      .then((data) => {
        return setDogList(data["message"]);
      });

    setSelectedDog(["affenpinscher"]);

    fetch(`https://dog.ceo/api/breed/affenpinscher/images/random`)
      .then((response) => {
        return response.json();
      })
      .then((json) => {
        return setDogImage(json["message"]);
      });
  };

  //初回マウント時に実行する処理を記載
  useEffect(() => {
    return getAllDogList;
  }, []);

  return (
    <div style={{ width: "100%" }}>
      <div style={{ display: "flex", alignItems: "center" }}>
        <p
          style={{
            backgroundColor: "gray",
            color: "#fff",
            padding: "10px 6px",
          }}
        >
          <span>https://dog.ceo/api/breed/</span>
          <select
            style={{ padding: "10px 40px" }}
            onChange={(e: ChangeEvent<HTMLSelectElement>) => {
              handleFormDogList(e);
            }}
          >
            {Object.entries(dogList).map(([breed, subBreedArr]) => {
              if (subBreedArr.length === 0) {
                return <option key={breed}>{breed}</option>;
              } else {
                return subBreedArr.map((subBreed: string) => {
                  const dogName: string = `${subBreed} ${breed}`;
                  return <option key={dogName}>{dogName}</option>;
                });
              }
            })}
          </select>
          <span>/images/random</span>
        </p>
        <button
          style={{
            backgroundColor: "blue",
            color: "#fff",
            marginLeft: "6px",
            padding: "15px 30px",
          }}
          onClick={onClickShowDog}
        >
          fetch
        </button>
      </div>

      <div style={{ marginTop: "20px" }}>
        <img style={{ width: "650px", height: "650px" }} src={dogImage}></img>
      </div>
    </div>
  );
};

```

</details>

## 各説明
### 1-①犬種リストの取得
まず初回マウント時の処理をuseEffectで記述しています。
```react
  //初回マウント時に実行する処理を記載
  useEffect(() => {
    return getAllDogList;
  }, []);
```

実際に行う処理は`getAllDogList`に書かれています。
ドロップダウンリストで使う犬種情報を取得するために`"https://dog.ceo/api/breeds/list/all"`を叩きます。
```react
//画面ロード時にドロップダウンリストに入れる犬種情報の取得、画像の初期表示
  const getAllDogList = () => {
    //犬種情報の取得
    fetch("https://dog.ceo/api/breeds/list/all")
      .then((response) => {
        return response.json();
      })
      .then((data) => {
        return setDogList(data["message"]);
      });

    //最初に選択される犬種と表示画像の管理
    setSelectedDog(["affenpinscher"]);

    fetch(`https://dog.ceo/api/breed/affenpinscher/images/random`)
      .then((response) => {
        return response.json();
      })
      .then((json) => {
        return setDogImage(json["message"]);
      });
  };
```
取得した情報は以下のJSONで返却されます。
```json:JSON
{
        "affenpinscher": [],
        "african": [],
        "airedale": [],
        "akita": [],
        "appenzeller": [],
        "australian": [
            "kelpie",
            "shepherd"
        ],
        
        (省略)
        
}
```

### 1-②取得したJSON情報をドロップダウンリストに表示する
ドロップダウンリストの実装箇所です。
```react:ドロップダウンリスト
<select
            style={{ padding: "10px 40px" }}
            onChange={(e: ChangeEvent<HTMLSelectElement>) => {
              handleFormDogList(e);
            }}
          >
            {Object.entries(dogList).map(([breed, subBreedArr]) => {
              if (subBreedArr.length === 0) {
                return <option key={breed}>{breed}</option>;
              } else {
                return subBreedArr.map((subBreed: string) => {
                  const dogName: string = `${subBreed} ${breed}`;
                  return <option key={dogName}>{dogName}</option>;
                });
              }
            })}
</select>
```
リストには取得したJSONの`key`を表示し、`value`が空配列でなければ`key`と`value`を連結した文字列を表示することにします。（後の画像情報取得APIを叩くのに必要）

上記を実現するのに、`dogList`(取得したJSON)の中身をmapでぐるぐるして表示させたい。
でも、mapって配列に対してしか使えない。そんな時は...

***Object.entries(オブジェクト名)*** を使いましょう。

`key`と`value`の組を要素に持つ配列を返してくれます。

上記の方法でオブジェクトの情報を配列で取得することが出来たので、あとはmapでぐるぐるしてあげて`optionタグ`でリストに表示を行っています。

### 2-① ドロップダウンリストで選択した犬種情報を管理する
リストで選択した情報は`onChangeイベント`で選択を検知して管理してあげます。
```react:ドロップダウンリスト
<select
            style={{ padding: "10px 40px" }}
            onChange={(e: ChangeEvent<HTMLSelectElement>) => {
              handleFormDogList(e);
            }}
          >
           (省略)
</select>
```
```react:onChangeイベント発火時の処理
 //ドロップダウンで犬種を選択したときに情報をsetする
  const handleFormDogList = (event: ChangeEvent<HTMLSelectElement>): void => {
    const selectedDogData: string = event.target.value;
    const breedInfo: string[] = selectedDogData.split(" ");
    return setSelectedDog(breedInfo);
  };
```
このあと画像情報取得API(`https://dog.ceo/api/breed/${breed}/${subBreed}/images/random`)を叩くのにあたり、
連結してリストに表示した`key`と`value`を再度分離してURLに含む必要があるので`split`を使っています。


### 2-②fetchボタン押下時に画像情報を取得し、取得画像を表示する
fetchボタン押下時に`onClickイベント`を発火させます。
```react:fetchボタン
<button
          style={{
            backgroundColor: "blue",
            color: "#fff",
            marginLeft: "6px",
            padding: "15px 30px",
          }}
          onClick={onClickShowDog}
        >
          fetch
</button>
    
```
```react:onClickイベント発火時の処理
//fetchボタンを押下したときにAPIをたたいて画像情報をsetする
  const onClickShowDog = () => {
    //splitの関係で分割代入がうまくいかないので分岐して対応
    if (selectedDog.length === 1) {
      const [breed] = selectedDog;
      fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
        .then((response) => {
          return response.json();
        })
        .then((json) => {
          return setDogImage(json["message"]);
        });
    } else {
      const [subBreed, breed] = selectedDog;
      fetch(`https://dog.ceo/api/breed/${breed}/${subBreed}/images/random`)
        .then((response) => {
          return response.json();
        })
        .then((json) => {
          return setDogImage(json["message"]);
        });
    }
  };
```
```react:画像表示場所
<div style={{ marginTop: "20px" }}>
        <img style={{ width: "650px", height: "650px" }} src={dogImage}></img>
</div>
```

## おわり
備忘録でした。
`useEffect`の使い方、オブジェクトの中身を配列で取得、`map`がネストするときに`return`書き忘れがち、といったことは経験を通した学びとなりました。
ドロップダウンの`onChangeイベント`で検知するイベントの型が`ChangeEvent<HTMLSelectElement>`ということも知りました。`Typescript`も満足に使えるようになりたいですね...