【React / TypeScript】ローディングを実装する方法

画面を読み込むときにAPIを呼び出してデータが取得されるまでローディングマーク(丸がぐるぐるするやつ)を表示したい、という場面は多いでしょう。

今回はReact + TypeScript + react-loadingを使って実装します。
また、APIを実装するのは手間なので以下のAPIを叩いてデータを取得します。

``` https://api.thecatapi.com/api/images/get?format=json&results_per_page=10&type=gif ``` (可愛い猫ちゃんの画像が取得されます。)

### react-loadingとは？

ローディングアイコンを実装するためのサードパーティ製ライブラリです。

- [npmのページ](https://www.npmjs.com/package/react-loading)
- [GitHubのリポジトリ](https://github.com/fakiolinho/react-loading)

最新のコミットが2年前なので開発はほぼ止まっているようです。


以下サンプルコードのようにpropsで色やアイコンの大きさを調整することができます。

```jsx
import React from 'react';
import ReactLoading from 'react-loading';
 
const Example = ({ type, color }) => (
    <ReactLoading type={type} color={color} height={667} width={375} />
);
 
export default Example;
```


### 環境

- TypeScript 4.8.4
- React 18.0.21
- react-loading 2.0.3
- Ubuntu20.04




### 事前準備

まずはreactのプロジェクトを作成します。


```bash
$ npx create-react-app --template typescript loading
```

続いて、react-loadingをプロジェクトにインストールします。

```bash
$ cd loading
$ npm install react-loading
```
正常にインストールできれば準備完了です。

### ディレクトリ構成

ディレクトリ構成は以下の通りです。

```
loading
├── src
　   ├── components
        ├── image.ts # APIのレスポンスの型を定義するファイル
        └── Images.tsx # 画面を描画するコンポーネント
        └── useFetchImages.tsx # APIから画像を取得するカスタムフック
```

### 実装例

#### APIから取得するデータの型定義

``` https://api.thecatapi.com/api/images/get?format=json&results_per_page=10&type=jpg ``` で取得できるjsonデータは「id」「url」「source_url」という3つのプロパティを持ちます。(型はいずれもstring)

レスポンスの型をImageとして定義しましょう。

```ts
export type Image = {
  id: string;
  url: string;
  source_url: string;
};
```

### 画面を描画する

```tsx

import  { useEffect} from 'react';
# react-loadingをインポートする
import ReactLoading  from "react-loading";

# Image型をインポート
import { Image } from './image';
import { useGetImages } from './useGetUsers';



export const Images = () => {
  const { isLoading, getImages, images } = useGetImages();
  useEffect(() => getImages(), []);

  return (
    <>
      {isLoading ?
        <ReactLoading
          type="spin"
          color="black"
          height="20px"
          width="20px"
          className="mx-auto"
        /> : 
        images.map((image: Image) => (
          <div key={ image.id}>
            <img src={image.url} style={ {width:"150px", height:"150px"}} />
          </div>
        ))
      }
    </>
  );
};
```

### APIから画像を取得するカスタムフック

```tsx

import axios from 'axios';
import { useCallback, useState } from 'react';
import { Image } from './image';

export const useGetImages = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [images, setImages] = useState<Image[]>([]);

  const getImages = useCallback(() => {
    setIsLoading(true);
    axios.get<Image[]>('https://api.thecatapi.com/api/images/get?format=json&results_per_page=10&type=jpg')
      .then((res) => {
        setTimeout(() => {
          setImages(res.data);
          setIsLoading(false)
        }, 3000);
      })
      .catch(() => {
        alert('にゃーん');
        setIsLoading(false);
      })
  },[images]);

  return { isLoading, getImages, images };
};
```

重要なポイントは以下の3点です。

- ローディング状態をboolean型のState isLoadingとして定義する。(ローディング中：true, ローディング中ではない：false )
- API呼び出しを開始したらisLoadingをtrueに切り替える。成否にかかわらずAPI呼び出しが完了したらisLoadingをfalseに変更する。
- Image.tsxではisLoadingがtrueであるときにローディングアイコンを表示する。isLoadingがfalseであるときには取得したデータを表示する。

また、API呼び出しやisLoading Stateの状態更新のようなUI描画と直接関係のない処理はカスタムフックに切り出すとImage.tsxの見通しがよくなります。

### 参考資料

[Reactに入門した人のためのもっとReactが楽しくなるステップアップコース完全版](https://www.udemy.com/course/react_stepup/)
[react-loading -npm](https://www.npmjs.com/package/react-loading)
[独自フックの作成](https://ja.reactjs.org/docs/hooks-custom.html)