【React】useStateとuseRecoilでの状態管理の違い

# はじめに
useStateとuseRecoilでの状態管理の違いについて雑にまとめてみました。

# ざっくりと
- useStateはコンポーネント内で状態管理することができる
- useRecoilは複数のコンポーネント間で状態管理することができる

親子関係にあるコンポーネント間で状態管理を共有したい場合、useStateを使うとpropsを渡す必要があるけれどuseRecoilを使うとpropsを渡さなくていいよということになります。

例題として以下2つのコンポーネントをChatgptに考えてもらいました。
- 所持金の増減ができるMannyApp.tsx
- 所持金の2倍やリセットができるOtherComponent.tsx

MannyApp.tsxを親コンポーネント、OtherComponent.tsxを子コンポーネントとします。

これらをtop画面にまとめて表示させたものがこちらです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/c225bfbb-079d-fb69-fad1-47cc06006b62.png)

増やすボタンや2倍にするボタンを押したときの違いを見ていきたいと思います。

# useState
## 親コンポーネント
所持金をbalanceという変数で状態管理し、後述の子コンポーネントに渡しています。

```tsx:MannyApp.tsx
import { Box, Button } from '@chakra-ui/react'
import React, { useState } from 'react'
import { balanceState } from '../atoms/state';
import { useRecoilState } from 'recoil';
import OtherComponent from './OtherComponent';

export const MoneyApp = () => {
    const [balance, setBalance] = useState(0);

    const increaseBalance = () => {
        setBalance(balance + 1);
    };

    const decreaseBalance = () => {
        setBalance(balance - 1);
    };

  return (
    <div>
        <Box>親コンポーネント</Box>
        <Box>親コンポーネントの所持金：{balance}円</Box>
        <Button onClick={increaseBalance}>増やす</Button>
        <Button onClick={decreaseBalance}>減らす</Button>
        <OtherComponent balance={balance}/>
    </div>
  )
}
```

## 子コンポーネント
親コンポーネントからpropsで受け取ったbalanceを状態管理しています。

```tsx:OtherComponent.tsx
import React, { useEffect, useState } from 'react';
import { Box, Button } from '@chakra-ui/react';

type Props = {
    balance: number
}

const OtherComponent = (props: Props) => {
    const { balance } = props;
    const [otherBalance, setOtherBalance] = useState(balance);

    useEffect(() => {
        setOtherBalance(balance);
      }, [balance]);
    
  const doubleBalance = () => {
    setOtherBalance(otherBalance * 2);
  };

  const resetOtherBalance = () => {
    setOtherBalance(0);
  };

  return (
    <div>
      <Box>子コンポーネント</Box>
      <Box>親コンポーネントの所持金: {balance} 円</Box>
      <Button onClick={doubleBalance}>2倍にする</Button>
      <Button onClick={resetOtherBalance}>リセット</Button>
      <Box>子コンポーネントの所持金: {otherBalance} 円</Box>
    </div>
  );
};

export default OtherComponent;
```
初期値にbalanceを指定しているので、
親の所持金を増やすと、子コンポーネントでの親と子の所持金にも増分が反映されていますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/7748c943-8e9f-99ff-1b2d-70316f041f70.png)

2倍にすると子コンポーネントの所持金だけ更新されていますね。
これはuseStateを使っているため、子コンポーネントで管理している状態を親コンポーネントが検知できないからです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/74a814d4-ca9d-9cdf-8561-46a1c862057b.png)

リセットも同様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/302a9337-4acd-07ce-b372-7b2fe76a47ab.png)

このようにuseStateを使用する場合は親から子にpropsを渡し、それぞれのコンポーネント内で状態管理を行う必要があります。子コンポーネントで更新した状態を親コンポーネントで参照しないのであればこれで問題ないです。
親コンポーネントでも参照したい場合はuseRecoilを使う必要があります。

# useRecoil
useRecoilを使用するとコンポーネント間で状態管理を共有することができます。
つまりpropsの受け渡しが不要になり、どのコンポーネントでbalanceの状態を参照しても同じ値となります。

まずはrecoilを導入します。
```
$ yarn add recoil
```

忘れずにindex.tsxのAppをRecoilRootで囲います。

```tsx:index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { ChakraProvider } from '@chakra-ui/react';
import { RecoilRoot } from 'recoil';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <ChakraProvider>
      <RecoilRoot>
        <App />
      </RecoilRoot>
    </ChakraProvider>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
```

atomsを定義します。

```tsx:state.tsx
import { atom } from 'recoil';

export const balanceState = atom<number>({
  key: 'balanceState',
  default: 0,
});
```
## 親コンポーネント
useStateの代わりにuseRecoilStateを使ってbalanceの状態管理をしています。
このコンポーネントでbalanceの状態が更新されたとき、
他のコンポーネントでuseRecoilStateを使用すると更新されたbalanceを参照することができます。

```tsx:MannyApp.tsx
import { Box, Button } from '@chakra-ui/react'
import { balanceState } from '../atoms/state';
import { useRecoilState } from 'recoil';
import OtherComponent from './OtherComponent';

export const MoneyApp = () => {
    const [balance, setBalance] = useRecoilState(balanceState)

    const increaseBalance = () => {
        setBalance(balance + 1);
    };

    const decreaseBalance = () => {
        setBalance(balance - 1);
    };

  return (
    <div>
        <Box>親コンポーネント</Box>
        <Box>親コンポーネントの所持金：{balance}円</Box>
        <Button onClick={increaseBalance}>増やす</Button>
        <Button onClick={decreaseBalance}>減らす</Button>
        <OtherComponent />
    </div>
  )
}
```

# 子コンポーネント
useStateと違い、propsを受け取っていません。
useRecoilStateを使ってグローバルにbalanceの状態を取得しています。

```tsx:OtherComponent.tsx
import { useRecoilState } from 'recoil';
import { balanceState } from '../atoms/state';
import { Box, Button } from '@chakra-ui/react';

const OtherComponent = () => {
      const [balance, setBalance] = useRecoilState(balanceState);

  const doubleBalance = () => {
    setBalance(balance * 2);
  };

  const resetOtherBalance = () => {
    setBalance(0);
  };

  return (
    <div>
      <Box>子コンポーネント</Box>
      <Box>親コンポーネントの所持金: {balance} 円</Box>
      <Button onClick={doubleBalance}>2倍にする</Button>
      <Button onClick={resetOtherBalance}>リセット</Button>
      <Box>子コンポーネントの所持金: {balance} 円</Box>
    </div>
  );
};

export default OtherComponent;
```

親の所持金を増やすとuseState同様に子の所持金も更新されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/e4df3216-2d03-a82d-b1fb-276431e7e1e5.png)


しかし子の所持金を2倍にしたりリセットするとuseStateのときとは違い、親の所持金にも更新が反映されています。
これは子コンポーネントと親コンポーネント間で状態管理の共有ができているからです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/013fabcb-3dd7-358d-89ef-0ea0661888eb.png)

# おわりに
useRecoilすごいです。