状態管理 recoil使ってみた。


こんにちは株式会社Atraeでフロントエンドエンジニアをしている加藤です。
この記事は、[Atrae Advent Calendar 2020](https://qiita.com/advent-calendar/2020/atrae)17日目の記事です。
業務でrecoilを使う機会があったので、recoilについてお話ししようと思います。


# Recoil
Facebookが開発した状態管理ライブラリ。
react hooksで使われる事が前提の設計となっている。
非同期処理にも対応しているみたいです。
reduxは、最初から丁寧な設計をしないと後から変更する事が難しい（既に1つの木構造になってしまっている)ですがrecoilはstate(atoms)を分ける事ができるので管理しやすいです。

詳しくは下記Recoilのドキュメントをご覧ください。
https://recoiljs.org


# Recoilの導入
recoilをインストールする

```
yarn add recoil
```


```__app.tsx
import { NextPage } from 'next';
const App: NextPage<AppProps> = ({ Component, pageProps }) => {
  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  );
};
export default App;
```

# atom
デフォルト値とキーを設定します。
キーはユニークになるようにします。

```atom.tsx
export const TextAtom = atom({
  key: "TextAtom"
  default: text: '',
});
```

作成したatom(TextAtom)をuseRecoilStateに渡します。
useStateと同じ使い方で使用できます。
useRecoilStateの他にuseRecoilValueとuseSetRecoilStateがあります。
useRecoilValueは読み取り専用です。
useSetRecoilStateは書き込み専用です。


```a.tsx
  const A: FunctionComponent = () => {
  const [text, setText] = useRecoilState(TextAtom);
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };
  return (
    <div>
      <input value={text} onChange={(e) => handleInputChange(e)} />
    </div>
  );
};

export default A;
```





# 違う画面で同じ関数を使いたい場合
A画面,B画面で同じ関数を使いたい場合(更新したいstateが別)があるかと思います。
共通で使いたい関数をまとめます。(カスタムHooksを作成します)
あとはhandleInputChange()をA画面B画面で呼び出して使うだけです。


```atom.tsx
export const TextAtom = atom({
  key: "TextAtom",
  default: {
    textAtomA: '',
    textAtomB: '',
  },
});
```
```useTextState.tsx
export const useTextState = () => {
  const [text, setText] = useRecoilState();
  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, key: string) => {
    setText({ ...text, [key]: e.target.value });
  };
  return {
    handleInputChange,
  };
};
```
複雑な処理を共通化したい場合は複雑な処理の値の変換部分を関数化します。
ドキュメントをご覧ください。
https://recoiljs.org/docs/basic-tutorial/atoms/

```item.tsx
function TodoItem({item}) {
  const [todoList, setTodoList] = useRecoilState(todoListState);
  const index = todoList.findIndex((listItem) => listItem === item);

  const editItemText = ({target: {value}}) => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      text: value,
    });

    setTodoList(newList);
  };

  const toggleItemCompletion = () => {
    const newList = replaceItemAtIndex(todoList, index, {
      ...item,
      isComplete: !item.isComplete,
    });

    setTodoList(newList);
  };

  const deleteItem = () => {
    const newList = removeItemAtIndex(todoList, index);

    setTodoList(newList);
  };

  return (
    <div>
      <input type="text" value={item.text} onChange={editItemText} />
      <input
        type="checkbox"
        checked={item.isComplete}
        onChange={toggleItemCompletion}
      />
      <button onClick={deleteItem}>X</button>
    </div>
  );
}

function replaceItemAtIndex(arr, index, newValue) {
  return [...arr.slice(0, index), newValue, ...arr.slice(index + 1)];
}

function removeItemAtIndex(arr, index) {
  return [...arr.slice(0, index), ...arr.slice(index + 1)];
}
```

#おわり

使ってみて思ったことは、recoil自体使われはじめてから時間が経っておらず
ベストプラクティスな設計はまだよくわかってないです。
ただreduxよりもシンプルに実装ができ学習コストも高くないのは良かったです！