Recoilを基礎から理解しよう！(Selector編)

この記事は

- Reactで状態管理ツールを使ってみたい。
- でも学習コストが高くて難しそう。

そのように感じている人向けに、Recoilの使い方をまとめたものになっています。

- [Recoilを基礎から理解しよう！(環境構築・Atom編)](https://qiita.com/takusan64/items/bdb61b46d46395e913df)
- [Recoilを基礎から理解しよう！(Selector編)]()　:point_left_tone2: now

# 注意書き
本記事は「[Recoilを基礎から理解しよう！(環境構築・Atom編)](https://qiita.com/takusan64/items/bdb61b46d46395e913df)」で環境構築を行った、`Next.js`で動作確認を行います。
よろしければ前回の記事を参考に環境構築を行ってください！

# Selectorって何？
`Selector`は前回説明した`Atom`や、他の `Selector`を参照し、新しい値を作成する機能になります。
また作成したSelectorは参照元の値を更新することができ、更新時にはSelectorの値の再計算を行います。
(参照元が更新されると、作成したSelectorが自動計算される仕組みになっています。)

下記は体重の`Atom`を参照する`Selector`を作成した例になります。

```js
// 体重のAtomを作成
const weightState = atom({
  key: "weight",
  default: 50
});

// Selectorを作成
const weightUnitState = selector({
  key: 'weightUnit',

  // 参照先を元に新しい値を作成する場合は、「get」を利用します
  get: ({ get }) => {
    // 体重のAtomを参照
    const weight = get(weightUnitState);
    const unit = 'kg';

    // 単位(kg)付きの体重をreturn
    return `${weight}${unit}`;
  },

  // 参照先の値を更新する場合は、「set」を利用します
  set: ({ set }, newValue) => {
    set(weightState, newValue)
  }
});
```

https://recoiljs.org/docs/basic-tutorial/selectors

# 実装
`Atom`と`Selector`の動きを確認するために、実際に書いてみましょう！

## データを表示・更新させる
先程の例で利用した体重の値を、Recoilの状態として登録しましょう。
`/recoil/weight.js`を作成し、どのディレクトリでも利用できるようexportしておきましょう。

```jsx:/recoil/weight.js
import { atom, selector } from "recoil"

// 体重のAtomを作成
export const weightState = atom({
  key: "weight",
  default: 50
});

// Selectorを作成
export const weightUnitState = selector({
  key: 'weightUnit',

  // 参照先を元に新しい値を作成する場合は、「get」を利用します
  get: ({ get }) => {
    // 体重のAtomを参照
    const weight = get(weightState);
    const unit = 'kg';

    // 単位(kg)付きの体重をreturn
    return `${weight}${unit}`;
  },

  // 参照先の値を更新する場合は、「set」を利用します
  set: ({ set }, newValue) => {
    set(weightState, newValue)
  }
});
```

作成した`Selector`から、`Atom`と`Selector`両方の値を更新してみましょう。

`/pages/weight.js`を作成し、`Atom`と`Selector`の値を表示・更新させます。
`Selector`も`Atom`と同様に、`useRecoilState()`を用いて更新することができます。
(`Atom`は`useRecoilValue ()`を用いて、読み取り専用にしています。)

```jsx:/pages/weight.js
import { useState } from "react"
import { useRecoilState, useRecoilValue } from "recoil";
import { weightState, weightUnitState } from "../recoil/weight"

export default function Weight() {
  // 体重のAtomを呼び出し(読み取り専用)
  const weightAtom = useRecoilValue(weightState)
  // Selectorを呼び出し
  const [weightSelector, setWeightSelector] = useRecoilState(weightUnitState)
  
  const [weight, setWeight] = useState(0)

  // Selectorを用いて体重を更新する
  const updateWeight = (newWeight) => {
    setWeightSelector(newWeight)
    setWeight(0)
  }

  const handleChange = (e) =>{
    setWeight(e.target.value)
  }

  return (
    <div>
      <p>Atomの値:{weightAtom}</p>
      <p>Selectorの値:{weightSelector}</p>
      <div>
        <input type="number" value={weight} onChange={handleChange} />
        <button onClick={() => updateWeight(weight)}>
          Update Weight
        </button>
      </div>
    </div>
  )
}

```

`Next.js`を起動させましょう。
```sh
npm run dev
```

[localhost:3000/weight](localhost:3000/weight)にアクセスすると、`Selector`から`Atom`と`Selector`両方の値更新を確認できると思います！

![体重.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469819/03f2d628-b8fb-4e03-36fe-cb194dc5d975.gif)


## 非同期処理でデータを取得する
Atomは非同期処理を扱うことはできませんが、`Selector`は非同期処理も扱うことができます。
非同期のSelectorを利用する際、`useRecoilValue()`や`useRecoilState()`は、Promiseが完了していれば値を返し、完了していなければPromiseオブジェクトをthrowします。
これは`React.Suspence`で扱えるようにするために、このような設計になっているのだと思います。

https://ja.reactjs.org/docs/react-api.html#reactsuspense

非同期処理を作成するので、`axios`をインストールしておきましょう。

```sh
npm install --save axios
```

インストールが完了したら、APIからデータfetchを行うRecoilの状態を作成します。
今回はみんな大好き「[Qiita API](https://qiita.com/api/v2/docs)」を利用させてもらいます。
`/recoil/qiita.js`を作成し、その中に`Selector`を作成します。
(今回のように`Atom`の中に`Selector`を入れる書き方もできますので、コード設計に合わせて使い分けてみてください！)

```js:/recoil/qiita.js
import { atom, selector } from "recoil"
import axios from "axios";

export const qiitaListState = atom({
  key: 'qiitaList',
  default: selector({
    key: 'getQiitaList',
    get: async ({get}) => {
      try {
        // 20件の、Qiita記事情報を取得
        const response = await axios('https://qiita.com/api/v2/items?page=1&per_page=20');
        const qiitaList = await response.data.map((v)=>v.title)
        return qiitaList;
      } catch (error) {
        throw error;
      }
    }
  })
});
```

次に`/pages/qiita.js`を作成し、取得したQiitaのタイトルを画面に表示させます。

本当は`React.Suspence`と`useRecoilValue()`を利用して、Qiitaのタイトルを表示させたかったのですが、**2022 3/13時点**で`Next.js`は`React.Suspence`をまだサポートしていません。(試験的には行っているようですが、正式版ではまだですね。)

https://nextjs.org/docs/advanced-features/react-18/overview

そのような時のために、Recoil側で`useRecoilValueLoadable()`という関数を用意してくれていますのでこれを使って見たいと思います。

https://recoiljs.org/docs/api-reference/core/useRecoilValueLoadable/

```jsx:/pages/qiita.js
import { useRecoilValueLoadable } from "recoil";
import { qiitaListState } from "../recoil/qiita"

export default function Qiita() {
  // Qiita記事情報を呼び出し
  const qiitaList = useRecoilValueLoadable(qiitaListState)

  // fetchの状態によって分岐
  switch (qiitaList.state) {
    case "hasError":
      throw qiitaList.contents;
    case "loading":
      return <div>Loading...</div>;
    case "hasValue":
      return (
        <div>
          <p>Qiita List</p>
          <ul>
            {qiitaList.contents.length && qiitaList.contents.map((v,i) => (
              <li key={i}>{v}</li>
            ))}
          </ul>
        </div>
      );
  }
}
```
[localhost:3000/qiita](localhost:3000/qiita)にアクセスすると、非同期通信が行われているのを確認できると思います！
![Qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469819/25807994-cc83-433a-39ab-17ff8b53b9d5.gif)

<details><summary>「React.Suspence」がサポートされたらこんな感じになります！</summary>

```jsx
import { Suspense } from "react";
import { useRecoilValue } from "recoil";
import { qiitaListState } from "../recoil/qiita"

export default function Qiita() {
  // Qiita記事情報を呼び出し
  const qiitaList = useRecoilValue(qiitaListState)

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <div>
        <p>Qiita List</p>
        <ul>
          {qiitaList.contents.length && qiitaList.contents.map((v,i) => (
            <li key={i}>{v}</li>
          ))}
        </ul>
      </div>
    <Suspense>
  );
}
```

ロジックとコンポーネントが分離されていて見やすいですね！
（早くサポートされないかな...）

</details>

# 最後に
次回はRecoilのカスタムフック利用について書きたいと思います！

今回利用したコードはこちらになります！
https://github.com/takusan64/nextjs-recoil