Recoilで簡単なフィルター機能を実装

## デモ（codesandbox）

今回使用したコードです　→　[Demo](https://codesandbox.io/s/infallible-bassi-d42u0?file=/src/App.tsx)

## Recoilとは

meta社が開発したグローバルな状態管理をできるライブラリです。

正式リリースされたばかりだが、シンプルに状態管理をしていけるので最近注目度が高いです。
実際コードが少なく書けるので見通しもよいかと思います。
初学者でRedux使っていないので、どこがどう違って、なにが良くてなにが良くないか分からないですが。。。

## インストール

まずパッケージのインストールをします。typescriptの場合は型定義もインストール忘れずに。

```tsx
npm i recoil
npm i @types/recoil
```

## RecoilRootでコンポーネントをwrapする

Recoilで状態管理する場合、そのグローバルな状態管理をする範囲をRecoilRootで囲む必要があります。プロジェクト全体を範囲とする場合、大元の<App/>をwrapします。

```tsx
import { render } from "react-dom";
import { RecoilRoot } from "recoil";

import App from "./App";
//Recoilを利用するコンポーネントをRecoilRootでwrapする
const rootElement = document.getElementById("root");
render(
  <RecoilRoot>
    <App />
  </RecoilRoot>,
  rootElement
);
```

## atomの定義

グローバルStateとして扱いたいデータをatomとして定義する。

今回は男女でフィルターかけようと思うので、このような初期値を定義します。keyは一意のものを使用してください。コピペでファイル作成したときにkeyだけ変更し忘れて同じkeyができてしまってバグになっていたことがあります。

atomの定義はこれだけでグローバルなstateとして管理できます。非常にシンプル。

```tsx
import { atom } from "recoil";

type personType = { name: string; gender: string };

const person: personType[] = [
  { name: "yamada", gender: "male" },
  { name: "sato", gender: "female" }
];
//keyは一意のものを設定する
//defaultプロパティに初期値をセットするだけ
export const persons = atom({
  key: "person",
  default: person
});
//これでpersonsはグローバルなStateとして管理できる
```

## atomの使い方

atomを使いたい場合はuseRecoilStateを使います。定義の仕方はuseStateと同じ。setの仕方も同じですので非常に簡単に使えます。

```tsx
import { useRecoilState } from "recoil";
import { persons } from "./Atom";

export default function App() {
  //useStateと同じように定義し、初期値をatomとする
  const [personsArray, setPersonsArray] = useRecoilState(persons);
  console.log(personsArray);
  //[Object, Object]
 
  const onClickSet = () => {
    //Stateの更新もuseStateのときと同じようにできる
    setPersonsArray([...personsArray, { name: "suzuki", gender: "male" }]);
  };
  console.log(personsArray);
  // [Object, Object, Object]

  return <button onClick={onClickSet}>ボタン</button>;
}
```

用途に分けて
`const personsArray = useRecoilValue`とすると読み取り専用で定義ができます。
`const setPersonsArray=useSetRecoilState`とすると書き込み専用の定義ができます。

## selectorの定義

selectorはatomを加工した値をstateとして利用することができます。
atomが更新されるとselectorも更新がかかります。今回はfilter関数を使ってatomを男女に分けたselectorを作成します。

```tsx
import { selector } from "recoil";
import { persons } from "./Atom";

//getで取得したpersonsをfilter関数でmaleのみ抽出
export const malesSelector = selector({
  key: "males",
  get: ({ get }) => {
    const males = get(persons).filter((item) => {
      return item.gender === "male";
    });
    return males;
  }
});

//同様にfemaleのみ抽出
export const femalesSelector = selector({
  key: "females",
  get: ({ get }) => {
    const females = get(persons).filter((item) => {
      return item.gender === "female";
    });
    return females;
  }
});
```

このようにgetプロパティ内でatomを取得して何かしらの処理で値を加工できます。
returnした値が定義したselectorの値としてグローバルに読み取ることができます。
この場合のselectorは読み取り専用となります。
setプロパティはオプションであるのですが、この記事では割愛。

## selectorの読み込みとフィルター機能の実装

全体としてはこんな感じです。

（1）のようにuseRecoilValueを使ってatomのように取得できます。

あとはそれぞれの配列をmapで繰り返してリスト表示します。
どれを表示するかは別のstateを定義して、レンダリングの条件分岐などで表示を制御すれば、簡単なフィルター分けを実装できます。
条件分岐は＆＆を使って簡単に書いてます。

```tsx
export default function App() {
  //useStateと同じように定義し、初期値をatomとする
  const [personsArray, setPersonsArray] = useRecoilState(persons);
  console.log(personsArray);
  //[Object, Object]

  const onClickSet = () => {
    //Stateの更新もuseStateのときと同じようにできる
    setPersonsArray([...personsArray, { name: "suzuki", gender: "male" }]);
  };
  console.log(personsArray);
  // [Object, Object, Object]

  //定義したselectorはatomと同じように取得できる・・・（1）
  const malesArray = useRecoilValue(malesSelector);
  const femalesArray = useRecoilValue(femalesSelector);
  //ボタンを押したときのshowState
  const [showList, setShowList] = useState<string>("");

  const onClickAll = () => {
    setShowList("all");
  };
  const onClickMale = () => {
    setShowList("male");
  };
  const onClickFemale = () => {
    setShowList("female");
  };

  //レンダーの条件分岐は && を使用
  //&&の左側がtrueなら右側を評価する
  //map関数で配列を繰り返してリスト表示する
  return (
    <>
      <button onClick={onClickSet}>鈴木(男)を追加</button>
      <button onClick={onClickAll}>全て表示</button>
      <button onClick={onClickMale}>maleのみ表示</button>
      <button onClick={onClickFemale}>femaleのみ表示</button>
      {showList === "all" &&
        personsArray.map((item) => {
          return (
            <div>
              <ul>
                <li>{item.name}</li>
              </ul>
            </div>
          );
        })}
      {showList === "male" &&
        malesArray.map((item) => {
          return (
            <div>
              <ul>
                <li>{item.name}</li>
              </ul>
            </div>
          );
        })}
      {showList === "female" &&
        femalesArray.map((item) => {
          return (
            <div>
              <ul>
                <li>{item.name}</li>
              </ul>
            </div>
          );
        })}
    </>
  );
}
```

atomが追加や変更で更新された時のみselectorも更新されるので、その辺も特に意識する必要がなくとても簡単。
atomとselectorだけでも色々できるので、もし誰かの学習の参考になれば幸いです。