React Queryを使ったGlobal Server State マネジメント

![v2-055fd5bd248e4f83efd02778b9773254_1440w.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/796f55c7-72ac-4737-8013-21680ccb0a15.jpeg)
## 前書き
React Queryがv4になったタイミングで、正式な名称は`TanStack Query`になりましたが、この記事内では`React Query`と呼ばさせていただきます。
公式ドキュメント:

https://tanstack.com/query/v4

どんなwebシステムでもフロントからサーバーへのデータの取得、修正削除などのリクエストは必ず発生します。
`React Query`使用することで、何がどう便利になったのか、簡単なサンプルを見て、体感しましょう。:point_up_tone1:
サンプル少し長いので最後まで付き合っていただけると幸いです。:relaxed:

## サンプル

簡単なリストデータを取得することを想定しているサンプルになります。
* axiosで書いてますが、fetchでも使い方同じです。
```javascript
async function requestList() {
  const { data } = await axios.get("/api/list");
  return data;
}
```

4行のコードを書くだけで、サーバーからデータの取得はできます。:point_up_tone1:

次はこのリクエストを`フック`にして、使い勝手を改善します。

```jsx
function useListQuery() {
  const [data, setData] = React.useState([]);

  React.useEffect(() => {
    (async () => {
        const { data } = await axios.get("/api/list");

        setData(data);
    })();
  }, []);

  return {
    data,
  };
}

function List() {
  const { data } = useListQuery();
  
  return (
    <div>
     {data.map((item) => {
         return <div>{item}</div>
      })}
    </div>
  );
}
```
ここまで書いたら、一見良さげですが:relaxed:
リクエスト送った後のエラー、ローディング処理はまだのため、追加しておきます。:point_up_tone1:

```jsx
function useListQuery() {
  const [data, setData] = React.useState([]);
  const [isLoading, setLoading] = React.useState(false);
  const [error, setError] = React.useState();

  React.useEffect(() => {
    (async () => {
        setLoading(true);
        
        try {
          const { data } = await axios.get("/api/list");
          setData(data);       
        } catch(e) {
          setError(e);
        }
        setLoading(false); 
    })();
  }, []);

  return {
    data,
    isLoading,
    error,
  };
}
```
以上、これで良くあるリクエストを管理するフックが完成しました、それを実現するために25行のコードも書いてしまったのですが、

`React Query`使う場合、どうなるでしょう。

## React Queryサンプル

`React Query`を利用するためにはreact-queryパッケージをインストールする必要があります。

```
yarn add react-query
or
npm install react-query
```

先のフックを`React Query`使って書き直します。


```jsx
import { useQuery } from 'react-query';
...

function useListQuery() {
  const {data, isLoading, isError} = useQuery("list", async () => {
    const { data } = await axios.get("/api/list");
    return data;
  });

  return {
    data,
    isLoading,
    isError,
  };
}
```

これだけで完了です、かなりシンプルになったと思います:point_up_tone1:
`React Query`ではコードにある通り、第一引数に任意の名前の`ユニークキー`、第二引数には`promiseを戻す関数`を設定します。

```
const result = useQuery('ユニークキー', Promiseを返す関数)
```

`React Query`の**キャッシュ機能**はこのユニークキーによって実現している、一度取得したデータはしばらキャッシュされて、キーが変わるタイミングで、データを再度取得します。

ユニークキーは文字列以外、リストやオブジェクトにすることもできます。

```javascript: キーサンプル
useQuery('list', ...)
useQuery(['list'], ...)

useQuery(['list', 1], ...)
userQuery(['list', {
  page: 1
}])
useQuery({
  type: 'list',
  page: 1
})
```

それらを踏まえて、ユーザーごとのデータを取得する際に、ユニークキーの書き方は下記のコードのようになるのが適切です。

```jsx: ユーザーデーター取得サンプル
 const { data }  = useQuery(
    ['getuser', userID],
    () => axios.get(`/api/getuser/${userID}`).then(res => res.data);
)
```

さらに、`useQuery`の第三の引数に`{ enabled: userID !== undefined }`を追加すれば、`userID`渡さない限り、リクエストは送信しません。

```jsx: ユーザーデーター取得サンプル
 const { data }  = useQuery(
    ['getuser', userID],
    () => axios.get(`/api/getuser/${userID}`).then(res => res.data),
    { enabled: userID !== undefined }
)
```




データを変更する場合`useMutation`というフックがあります。

```jsx
import { useMutation } from "react-query"

function App() {
  const [isLoading, isError, mutate] = useMutation(() => axios.post("/api/list", { newList }));

  async function onSubmit() {
    await mutate()
  } 
  if (isLoading) {
     return <p>データ送信中...</p>;
  }
  if (isError) {
     return <p>エラー...</p>;
  }
  return <button onClick={onSubmit}>submit</button>;
}
```

その他の使い方は公式ドキュメント参考にしてください。

https://tanstack.com/query/v4/docs/guides/mutations

## キャッシュコントロール

### staleTimeとcacheTime

`React Query`は取得してきたデータを自動的にキャッシュする際に、条件を設定することができます。

- cacheTime: データをキャッシュする時間, デフォルトは5分。
- staleTime: キャッシュしたデータが古くなったとみなす時間,デフォルトは0。

例えばlistデータを取得し、その1分後再度listを取得し直す場合、すでにlistはキャッシュされているのでリクエストなしで表示可能です。
一方でstaleTimeが0のため、キャッシュを返した後にサーバーにリクエストを投げます。
データが新しくなっている場合はそちらの値に書き換えられます。

```jsx: サンプル
const { isLoading, isError, data } = useQuery(
  "list",
  async () => {
   const { data } = await axios.get("/api/list");
   return data;
 },
  { staleTime: 0, cacheTime: 5 * 60 * 1000 }
)
```

リクエストの送信を最小限に抑えたい場合、`staleTime` を `Infinity` に設定することができます。
そうすれば、キャッシュは常に新鮮なものとみなされるのでバックグラウンドでのフェッチは自動的には行われません。

:point_up_tone1:でも明らかデータ変更があった場合、最新のデータを表示したいときに`useQueryClient`を使うことができます。

### useQueryClient

`useMutation`使ってデータを変更した後に、`React Query`にキャッシュされてる古いデータを更新したい場合、
`useQueryClient`使って、手動でキャッシュの更新ができます。

仮に、useQueryを使って管理してるデータの中に、ユニックキーが`list`のものがあるとします、useMutationでそのデータに新たなデータを追加した際に、追加が成功すれば `queryClient`にキーを指定してデータを更新できます。

```jsx: useQueryClientサンプル
import {useQueryClient} from 'react-query'

const listForm = (data) => {
 ...
 const queryClient = useQueryClient()
 const { mutate } = useMutation(
   () => axios.post("/api/list", { newList }),
   { onSuccess: () => {
       queryClient.invalidateQueries(["list"])
     }
   }
 )

```
これで更新されたlistが表示されるようになると思います。

## 最後に
この記事はあくまで`React Query`使用する場合の雰囲気を伝わるもので、実際プロジェクトに導入する際に、ルートコンポネートに`QueryClientProvider`を追加するところから始めると思います、詳細は公式ドキュメントをご参考ください。

https://tanstack.com/query/v4/docs/quick-start