備忘録：SWRを少し触ってみた

# 1. SWRとは
https://swr.vercel.app/ja

SWRとはデータ取得のためのReact Hooksライブラリです。
APIからデータ取得する際、JavaScriptだけでは複雑になってしまう処理をかなりシンプルにまとめてくれます。
また、開発元がNext.jsと同じVercelなので、Next.jsとの相性も良い可能性が高いです。

今回はSWRを用いて簡単にデータの取得を行ってみたいと思います。

# 2. 環境構築
### 2-1. Next.jsプロジェクトの立ち上げ
今回はNext.jsを用いていこうと思いますので、Next.jsプロジェクトを立ち上げたいと思います。

https://nextjs.org/docs/getting-started#automatic-setup

```zsh:ターミナル
yarn create next-app
```
途中でプロジェクト名やTypeScriptを使用するか、ESLintを使用するか聞かれますが、お好みに合わせてください。
JavaScriptで記述していきたいと思います。


### 2-2. SWRのインストール
https://swr.vercel.app/ja/docs/getting-started#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

今回使用するSWRというパッケージをインストールしていきます。
```zsh:ターミナル
yarn add swr
```
package.jsonの中身を確認すると、swrという名前で依存関係が記述されています。
```json:package.json
{
  "name": "swr-sample",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "eslint": "8.29.0",
    "eslint-config-next": "13.0.6",
    "next": "13.0.6",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "swr": "^1.3.0"
  }
}
```

### 2-3. SWRを使用するページを用意
今回は、``yarn create next-app``で作成時に用意されているindex.jsに書き込んでいきたいと思います。
そのため、index.jsの中身を基本全てまっさらな状態にします。また、個人的な好みで拡張子をjsxにしています。

```javascript:pages/index.jsx
export default function Home() {
 
}
```
:::note info
jsでも問題なく動きますが、jsxにするとエディタが補完を強化してくれたりするのでおすすめです。
:::


# 3. SWRの雛形を書いてみる
### 3-1. fecher関数を用意する。
https://swr.vercel.app/ja/docs/getting-started#%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%88

SWRを使用するためにfetcher関数を用意する必要があります。
このfetcherとは、指定されたURLをキーとして、データを返却する非同期関数です。

```diff_javascript:pages/index.jsx
+ const fetcher = url => fetch(url).then(r => r.json())

export default function Home() {
}
```
:::note warn
以前はデフォルトのfetcherがあったため、自分で定義しなくてもデータ取得だけに関しては動かせたようですが、現在はデフォルトのfetcherが廃止されたため最初からきちんと定義しておかないと動きません。
ただし、fetcherがグローバルで提供されている場合は、この後使用するuseSWRのパラメータから省略できます。
:::

### 3-2. useSWR()を使用する

使用したい関数コンポーネント内にuseSWRを記述することで使用できます。
useSWRは先ほど依存関係に追加したswrパッケージからインポートします。
```diff_javascript:pages/index.jsx
+ import useSWR from 'swr';

const fetcher = url => fetch(url).then(r => r.json())

export default function Home() {
+  const { data, error } = useSWR(
+    '',
+    fetcher
+  );  
}
```

useSWRには2つの引数を渡します。

- 第1引数：APIエンドポイント
- 第2引数：fecher

返り値は``data``として渡され、例外が発生した際は``error``としてキャッチされています。

とりあえずこれで準備が整いました。

# 4. 天気予報 API（livedoor 天気互換）を叩いてみる
https://weather.tsukumijima.net/

https://github.com/tsukumijima/weather-api

### 4-1. 概要
今回使用するAPIはこちらの``天気予報 API（livedoor 天気互換）``です。
気象庁から配信されている全国各地の天気予報を取得し、JSON形式でデータを提供してくれます。

リクエストパラメータには``https://weather.tsukumijima.net/api/forecast``をベースに使用します。
続けて``city``というパラメータを指定し、該当する地域のIDを指定します。
すなわち以下のような形式でURLにリクエストを送ることでJSONが返却されます。
```plaintext:リクエストURLの形式
https://weather.tsukumijima.net/api/forecast/city/[id]
```
### 4-2. 取得したい地域のIDを調べる
今回は私が働いている愛知県名古屋市のお天気情報を取得してみたいと思います。
そのためにはまず、[全国の地点定義表](https://weather.tsukumijima.net/primary_area.xml)からIDを調べる必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663896/edc6380e-d842-2d0f-adb4-a2707cbb3e16.png)

今回欲しい名古屋のIDは``230010``なので、リクエストURLは以下のようになります。
```plaintext:名古屋のリクエストURL
https://weather.tsukumijima.net/api/forecast/city/230010
```

# 5. データをコンソールに出力してみる

### 5-1. APIのエンドポイントを渡す
```diff_javascript:pages/index.jsx
import useSWR from 'swr';

const fetcher = url => fetch(url).then(r => r.json())

export default function Home() {
  const { data, error } = useSWR(
+    'https://weather.tsukumijima.net/api/forecast/city/230010',
    fetcher
  );

+  console.log({ data, error });
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663896/93c3160d-c26a-771c-d6f9-41f828cc77e3.png)
最初にページがロードされたときはデータが取得されていないため、undefindとなっています。
その後、データがフェッチされ、``data``が入ってきているのがわかります。

:::note warn
本来であれば、ユーザーの通信速度等を加味してローディング処理を施すべきですが、本記事では割愛します。
:::

### 5-2. データの中身を確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663896/ff680945-6aa3-31cc-f87c-55e6e228e19a.png)
dataオブジェクトの中に色々とデータが入っていますが、シンプルに``data.forecasts``内のデータを画面に表示してみたいと思います。
```diff_javascript:pages/index.jsx
import useSWR from 'swr';

const fetcher = url => fetch(url).then(r => r.json())

export default function Home() {
  const { data, error } = useSWR(
    'https://weather.tsukumijima.net/api/forecast/city/230010',
    fetcher
  );

  console.log({ data, error });

  return (
+    <table>
+      <thead>
+        <tr>
+          <th colSpan={"2"}>日付</th>
+          <th>天気</th>
+          <th>最高気温</th>
+          <th>最低気温</th>
+        </tr>
+      </thead>
+      <tbody>
+        {data?.forecasts.map((day) => {
+          return (
+            <tr key={day.date}>
+              <td>{day.dateLabel}</td>
+              <td>{day.date}</td>
+              <td>{day.telop}</td>
+              <td>{day.temperature.max.celsius}°C</td>
+              <td>{day.temperature.min.celsius}°C</td>
+            </tr>
+          )
+        })}
+      </tbody>
+    </table>
  )
}
```

すると以下のように画面表示されます。
（適当にCSS当てています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663896/709bdb1a-71a8-be84-7e33-cc698e56ce46.png)

こんな感じに、簡単にデータを取得することができました。


# 6. エラーハンドリング
最後にエラー処理を記述していきます。
レスポンスのステータスコードが2xx番でないものは全てエラーと見做したいので、fecher関数を書き換えます。
```diff_javascript:pages.index.jsx
import useSWR from 'swr'

-const fetcher = (url) => fetch(url).then((r) => r.json())
+const fetcher = async (url) => {
+  const res = await fetch(url)
+
+  // もしステータスコードが 200-299 の範囲内では無い場合、
+  // レスポンスをパースして投げようとします。
+  if (!res.ok) {
+    throw new Error('データの取得に失敗しました。')
+  }
+  return res.json()
+}

export default function Swrsample() {
  const { data, error } = useSWR('https://weather.tsukumijima.net/api/forecast/city/230010', fetcher)

  console.log({ data, error })

   // 単純に404や500が返却された場合
+  if (error) {
+    return <div>{error.message}</div>
+  }
　  //地域IDが存在しないときに、dataの中にerrorとして格納されてくる場合
+  if (!error && data && data.error) {
+    return <div>{data.error}</div>
+  }

  return (
    <table>
      <thead>
        <tr>
          <th colSpan={'2'}>日付</th>
          <th>天気</th>
          <th>最高気温</th>
          <th>最低気温</th>
        </tr>
      </thead>
      <tbody>
        {data?.forecasts.map((day) => {
          return (
            <tr key={day.date}>
              <td>{day.dateLabel}</td>
              <td>{day.date}</td>
              <td>{day.telop}</td>
              <td>{day.temperature.max.celsius}°C</td>
              <td>{day.temperature.min.celsius}°C</td>
            </tr>
          )
        })}
      </tbody>
    </table>
  )
}

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663896/2ecd62ba-91b1-d49c-0d1a-010b1d135c6c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/663896/93ad8e69-61f9-6eca-f59f-d7214cc29d05.png)

二つとも正常にエラー処理できました。


# おわりに
ダラダラと書いてしまいましたが、最後まで読んで下さりありがとうございました。
他にも色々と処理しなければいけないことがたくさんあるかと思いますが、SWRによって記述がシンプルになることは実感できました。
他にもSWRには機能が豊富に用意されていますので、機会があれば触ってみたいと思います。