ReactQueryチートシート


## パッケージインストール

```
$ npm i react-query
```

## セットアップ

```tsx:app.tsx
import React from 'react'
import { QueryClient, QueryClientProvider } from 'react-query'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      suspense: true,
    },
  },
})

const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <SomethingComponent />
    </QueryClientProvider>
  )
}
export default MyApp
```


## カスタムフックの作成

### useQuery（データ取得用）

```ts:useFetchSomethingQuery
import { useQuery } from 'react-query'

type Something = {
  name: string
  email: string
}

type SomethingResponse = { data: Something[] }

// fetcher関数定義
const fetchGet = <T>(url: string): Promise<T> => {
  const options = {
    method: 'GET'
  }
  return fetch(url, options)
    .then((res) => {
      return res.json()
    })
    .catch((err) => {
      throw new Error(err)
    })
}

export const useFetchSomethingQuery = () => {
  const url = 'https://api.something.com/something'
  
  // useQueryの第一引数にはユニークなキーを設定
  return useQuery('fetchSomething', () =>
    fetchGet<SomethingResponse>(url)
  )
}
```

### useMutation（データ更新用）

```ts
import { useMutation, useQueryClient } from 'react-query'

type Something = {
  name: string
  email: string
}

type SomethingResponse = { data: { message: 'Success' } }

// fetcher関数定義
const fetchPost = <T>(url: string, data: Something): Promise<T> => {
  const options = {
    method: 'POST',
    body: JSON.stringify(data),
  }
  return fetch(url, options)
    .then((res) => {
      return res.json()
    })
    .catch((err) => {
      throw new Error(err)
    })
}

export const usePostSomethingMutation = () => {
  const queryClient = useQueryClient()
  const url = 'https://api.something.com/something'

  return useMutation((data: Something) => fetchPost<SomethingResponse>(url, data),
    {
      /*
        成功時にuseQueryのkeyを設定することで、キャッシュを破棄して自動で再フェッチしてくれる
        POSTした値を再フェッチしてリアルタイムで画面に描画したい時などに便利
      */
      onSuccess: () => {
        queryClient.invalidateQueries('fetchSomething')
      },
    }
  )
}

```

## コンポーネントで使う

### GET

```tsx:components/SomethingComponent
import React from 'react'
import useFetchSomethingQuery from '../api/useFetchSomethingQuery'

export const SomethingComponent = () => {

  const { data, isLoading } = useFetchSomethingQuery()
  const something = data?.data
 
  if (isLoading) return 'Loading...'
  
  return (
    <div>
      <h1>{something.name}</h1>
      <p>{something.email}</p>
    </div>
  )
}
```

### POST

```tsx:components/SomethingComponent
import React from 'react'
import usePostSomethingMutation from '../api/usePostSomethingMutation'

export const SomethingComponent = () => {

  const postMutation = usePostSomethingMutation()
  const data = {
    name: 'some',
    email: 'thing@.com'
  }

  const handleSubmit((data) => {
    // 成功時に何かしらの処理をしたい場合は第二引数に関数を渡す
    postMutation.mutate(
      data,
      { onSuccess: () => something() }
    )
  })
 
  if (postMutation.isLoading) return 'Loading...'
  
  return (
    <div>
      <button onClick={() => handleSubmit(data)}>
        POST!
      </button>
    </div>
  )
}
```

## Tips

### Suspenseを使う

- `defaultOptions`の`queries.suspense`をtrueに設定する必要がある
- 親コンポーネントで非同期処理をキャッチすることができる

```tsx
export const SomethingContainer = () => {

  return (
    <div>
      <Suspense fallback={'Loading...'}>
        <SomethingComponent />
      </Suspense>
    </div>
  )
}
```


### 再フェッチするトリガーを指定したい

- `useQuery`の第一引数は配列にすることができる
- 以下では`params`の値が変化したときに再フェッチを行う

```tsx
export const useFetchSomethingQuery = () => {
  const params = useParams()
  const url = 'https://api.something.com/something'
  
  return useQuery(['fetchSomething', params], () =>
    fetchGet<SomethingResponse>(url)
  )
}
```

## 参考資料

https://react-query-v3.tanstack.com/

https://tkdodo.eu/blog/mastering-mutations-in-react-query

https://zenn.dev/t_keshi/articles/react-query-prescription